SongZihuan пре 5 година
родитељ
комит
358ab787ab
100 измењених фајлова са 44956 додато и 0 уклоњено
  1. 1 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 12 0
      .idea/inspectionProfiles/Project_Default.xml
  4. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  5. 7 0
      .idea/misc.xml
  6. 8 0
      .idea/modules.xml
  7. 6 0
      .idea/vcs.xml
  8. 10 0
      .idea/弹簧加速度.iml
  9. 9 0
      README.md
  10. 35 0
      test.py
  11. 230 0
      venv/bin/Activate.ps1
  12. 76 0
      venv/bin/activate
  13. 37 0
      venv/bin/activate.csh
  14. 75 0
      venv/bin/activate.fish
  15. 8 0
      venv/bin/easy_install
  16. 8 0
      venv/bin/easy_install-3.8
  17. 8 0
      venv/bin/f2py
  18. 8 0
      venv/bin/f2py3
  19. 8 0
      venv/bin/f2py3.8
  20. 8 0
      venv/bin/pip
  21. 8 0
      venv/bin/pip3
  22. 8 0
      venv/bin/pip3.8
  23. 1 0
      venv/bin/python
  24. 1 0
      venv/bin/python3
  25. 1 0
      venv/bin/python3.8
  26. 3 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/DESCRIPTION.rst
  27. 1 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/INSTALLER
  28. 25 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/METADATA
  29. 9 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/RECORD
  30. 6 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/WHEEL
  31. 1 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/metadata.json
  32. 1 0
      venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/top_level.txt
  33. 558 0
      venv/lib/python3.8/site-packages/cycler.py
  34. 8 0
      venv/lib/python3.8/site-packages/dateutil/__init__.py
  35. 43 0
      venv/lib/python3.8/site-packages/dateutil/_common.py
  36. 4 0
      venv/lib/python3.8/site-packages/dateutil/_version.py
  37. 89 0
      venv/lib/python3.8/site-packages/dateutil/easter.py
  38. 61 0
      venv/lib/python3.8/site-packages/dateutil/parser/__init__.py
  39. 1609 0
      venv/lib/python3.8/site-packages/dateutil/parser/_parser.py
  40. 411 0
      venv/lib/python3.8/site-packages/dateutil/parser/isoparser.py
  41. 599 0
      venv/lib/python3.8/site-packages/dateutil/relativedelta.py
  42. 1735 0
      venv/lib/python3.8/site-packages/dateutil/rrule.py
  43. 12 0
      venv/lib/python3.8/site-packages/dateutil/tz/__init__.py
  44. 419 0
      venv/lib/python3.8/site-packages/dateutil/tz/_common.py
  45. 80 0
      venv/lib/python3.8/site-packages/dateutil/tz/_factories.py
  46. 1849 0
      venv/lib/python3.8/site-packages/dateutil/tz/tz.py
  47. 370 0
      venv/lib/python3.8/site-packages/dateutil/tz/win.py
  48. 2 0
      venv/lib/python3.8/site-packages/dateutil/tzwin.py
  49. 71 0
      venv/lib/python3.8/site-packages/dateutil/utils.py
  50. 167 0
      venv/lib/python3.8/site-packages/dateutil/zoneinfo/__init__.py
  51. BIN
      venv/lib/python3.8/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz
  52. 53 0
      venv/lib/python3.8/site-packages/dateutil/zoneinfo/rebuild.py
  53. 1 0
      venv/lib/python3.8/site-packages/easy-install.pth
  54. 5 0
      venv/lib/python3.8/site-packages/easy_install.py
  55. 1 0
      venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/INSTALLER
  56. 41 0
      venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/METADATA
  57. 6 0
      venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/RECORD
  58. 5 0
      venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/WHEEL
  59. 1 0
      venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/top_level.txt
  60. BIN
      venv/lib/python3.8/site-packages/kiwisolver.cpython-38-x86_64-linux-gnu.so
  61. 1 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1-py3.8-nspkg.pth
  62. 1 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/INSTALLER
  63. 127 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/METADATA
  64. 892 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/RECORD
  65. 5 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/WHEEL
  66. 1 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/namespace_packages.txt
  67. 3 0
      venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/top_level.txt
  68. BIN
      venv/lib/python3.8/site-packages/matplotlib/.libs/libpng16-cfdb1654.so.16.21.0
  69. BIN
      venv/lib/python3.8/site-packages/matplotlib/.libs/libz-a147dcb0.so.1.2.3
  70. 1601 0
      venv/lib/python3.8/site-packages/matplotlib/__init__.py
  71. 260 0
      venv/lib/python3.8/site-packages/matplotlib/_animation_data.py
  72. 1426 0
      venv/lib/python3.8/site-packages/matplotlib/_cm.py
  73. 1813 0
      venv/lib/python3.8/site-packages/matplotlib/_cm_listed.py
  74. 1147 0
      venv/lib/python3.8/site-packages/matplotlib/_color_data.py
  75. 726 0
      venv/lib/python3.8/site-packages/matplotlib/_constrained_layout.py
  76. BIN
      venv/lib/python3.8/site-packages/matplotlib/_contour.cpython-38-x86_64-linux-gnu.so
  77. BIN
      venv/lib/python3.8/site-packages/matplotlib/_image.cpython-38-x86_64-linux-gnu.so
  78. 711 0
      venv/lib/python3.8/site-packages/matplotlib/_layoutbox.py
  79. 2544 0
      venv/lib/python3.8/site-packages/matplotlib/_mathtext_data.py
  80. BIN
      venv/lib/python3.8/site-packages/matplotlib/_path.cpython-38-x86_64-linux-gnu.so
  81. BIN
      venv/lib/python3.8/site-packages/matplotlib/_png.cpython-38-x86_64-linux-gnu.so
  82. 129 0
      venv/lib/python3.8/site-packages/matplotlib/_pylab_helpers.py
  83. BIN
      venv/lib/python3.8/site-packages/matplotlib/_qhull.cpython-38-x86_64-linux-gnu.so
  84. 38 0
      venv/lib/python3.8/site-packages/matplotlib/_text_layout.py
  85. BIN
      venv/lib/python3.8/site-packages/matplotlib/_tri.cpython-38-x86_64-linux-gnu.so
  86. 21 0
      venv/lib/python3.8/site-packages/matplotlib/_version.py
  87. 530 0
      venv/lib/python3.8/site-packages/matplotlib/afm.py
  88. 1775 0
      venv/lib/python3.8/site-packages/matplotlib/animation.py
  89. 1633 0
      venv/lib/python3.8/site-packages/matplotlib/artist.py
  90. 2 0
      venv/lib/python3.8/site-packages/matplotlib/axes/__init__.py
  91. 8098 0
      venv/lib/python3.8/site-packages/matplotlib/axes/_axes.py
  92. 4441 0
      venv/lib/python3.8/site-packages/matplotlib/axes/_base.py
  93. 408 0
      venv/lib/python3.8/site-packages/matplotlib/axes/_secondary_axes.py
  94. 255 0
      venv/lib/python3.8/site-packages/matplotlib/axes/_subplots.py
  95. 2496 0
      venv/lib/python3.8/site-packages/matplotlib/axis.py
  96. 3421 0
      venv/lib/python3.8/site-packages/matplotlib/backend_bases.py
  97. 423 0
      venv/lib/python3.8/site-packages/matplotlib/backend_managers.py
  98. 1146 0
      venv/lib/python3.8/site-packages/matplotlib/backend_tools.py
  99. 54 0
      venv/lib/python3.8/site-packages/matplotlib/backends/__init__.py
  100. BIN
      venv/lib/python3.8/site-packages/matplotlib/backends/_backend_agg.cpython-38-x86_64-linux-gnu.so

+ 1 - 0
.gitignore

@@ -162,3 +162,4 @@ pyvenv.cfg
 .venv
 pip-selfcheck.json
 text.py
+venv

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 12 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,12 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="ignoredPackages">
+        <list>
+          <option value="PyQt5-stubs==5.13.1.4" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectPlainTextFileTypeManager">
+    <file url="file://$PROJECT_DIR$/README.md" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (弹簧加速度)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/弹簧加速度.iml" filepath="$PROJECT_DIR$/.idea/弹簧加速度.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 10 - 0
.idea/弹簧加速度.iml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/venv" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+# 基于python的物理弹簧仿真模型
+## 运行步骤
+1. 启动
+2. 运行计算,并且有pygame图示
+3. 快捷键``g``修改参数
+4. 关闭pygame窗口,输出matplotlib图表
+5. 关闭图表,运行结束
+## 注意
+运算会产生误差,可以调整``time``变小从而获得更大精度

+ 35 - 0
test.py

@@ -0,0 +1,35 @@
+import pygame, sys  # 声明 导入需要的模块
+
+from pygame.locals import *
+
+pygame.init()  # 初始化pygame
+
+DISPLAYSURF = pygame.display.set_mode((400, 300))  # 设置窗口的大小,单位为像素
+
+pygame.display.set_caption('Font')  # 设置窗口的标题
+
+# 定义几个颜色
+WHITE = (255, 255, 255)
+GREEN = (0, 255, 0)
+BLUE = (0, 0, 128)
+
+fontObj = pygame.font.Font('ZKST.ttf', 48)  # 通过字体文件获得字体对象
+textSurfaceObj = fontObj.render('Hello world!', True, (0,0,0))  # 配置要显示的文字
+textRectObj = textSurfaceObj.get_rect()  # 获得要显示的对象的rect
+textRectObj.center = (200, 150)  # 设置显示对象的坐标
+
+DISPLAYSURF.fill(WHITE)  # 设置背景
+
+DISPLAYSURF.blit(textSurfaceObj, textRectObj)  # 绘制字体
+
+while True:  # 程序主循环
+
+    for event in pygame.event.get():  # 获取事件
+
+        if event.type == QUIT:  # 判断事件是否为退出事件
+
+            pygame.quit()  # 退出pygame
+
+            sys.exit()  # 退出系统
+
+    pygame.display.update()  # 绘制屏幕内容

+ 230 - 0
venv/bin/Activate.ps1

@@ -0,0 +1,230 @@
+<#
+.Synopsis
+Activate a Python virtual environment for the current Powershell session.
+
+.Description
+Pushes the python executable for a virtual environment to the front of the
+$Env:PATH environment variable and sets the prompt to signify that you are
+in a Python virtual environment. Makes use of the command line switches as
+well as the `pyvenv.cfg` file values present in the virtual environment.
+
+.Parameter VenvDir
+Path to the directory that contains the virtual environment to activate. The
+default value for this is the parent of the directory that the Activate.ps1
+script is located within.
+
+.Parameter Prompt
+The prompt prefix to display when this virtual environment is activated. By
+default, this prompt is the name of the virtual environment folder (VenvDir)
+surrounded by parentheses and followed by a single space (ie. '(.venv) ').
+
+.Example
+Activate.ps1
+Activates the Python virtual environment that contains the Activate.ps1 script.
+
+.Example
+Activate.ps1 -Verbose
+Activates the Python virtual environment that contains the Activate.ps1 script,
+and shows extra information about the activation as it executes.
+
+.Example
+Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
+Activates the Python virtual environment located in the specified location.
+
+.Example
+Activate.ps1 -Prompt "MyPython"
+Activates the Python virtual environment that contains the Activate.ps1 script,
+and prefixes the current prompt with the specified string (surrounded in
+parentheses) while the virtual environment is active.
+
+
+#>
+Param(
+    [Parameter(Mandatory = $false)]
+    [String]
+    $VenvDir,
+    [Parameter(Mandatory = $false)]
+    [String]
+    $Prompt
+)
+
+<# Function declarations --------------------------------------------------- #>
+
+<#
+.Synopsis
+Remove all shell session elements added by the Activate script, including the
+addition of the virtual environment's Python executable from the beginning of
+the PATH variable.
+
+.Parameter NonDestructive
+If present, do not remove this function from the global namespace for the
+session.
+
+#>
+function global:deactivate ([switch]$NonDestructive) {
+    # Revert to original values
+
+    # The prior prompt:
+    if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
+        Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
+        Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
+    }
+
+    # The prior PYTHONHOME:
+    if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
+        Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
+        Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
+    }
+
+    # The prior PATH:
+    if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
+        Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
+        Remove-Item -Path Env:_OLD_VIRTUAL_PATH
+    }
+
+    # Just remove the VIRTUAL_ENV altogether:
+    if (Test-Path -Path Env:VIRTUAL_ENV) {
+        Remove-Item -Path env:VIRTUAL_ENV
+    }
+
+    # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
+    if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
+        Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
+    }
+
+    # Leave deactivate function in the global namespace if requested:
+    if (-not $NonDestructive) {
+        Remove-Item -Path function:deactivate
+    }
+}
+
+<#
+.Description
+Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
+given folder, and returns them in a map.
+
+For each line in the pyvenv.cfg file, if that line can be parsed into exactly
+two strings separated by `=` (with any amount of whitespace surrounding the =)
+then it is considered a `key = value` line. The left hand string is the key,
+the right hand is the value.
+
+If the value starts with a `'` or a `"` then the first and last character is
+stripped from the value before being captured.
+
+.Parameter ConfigDir
+Path to the directory that contains the `pyvenv.cfg` file.
+#>
+function Get-PyVenvConfig(
+    [String]
+    $ConfigDir
+) {
+    Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
+
+    # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
+    $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
+
+    # An empty map will be returned if no config file is found.
+    $pyvenvConfig = @{ }
+
+    if ($pyvenvConfigPath) {
+
+        Write-Verbose "File exists, parse `key = value` lines"
+        $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
+
+        $pyvenvConfigContent | ForEach-Object {
+            $keyval = $PSItem -split "\s*=\s*", 2
+            if ($keyval[0] -and $keyval[1]) {
+                $val = $keyval[1]
+
+                # Remove extraneous quotations around a string value.
+                if ("'""".Contains($val.Substring(0,1))) {
+                    $val = $val.Substring(1, $val.Length - 2)
+                }
+
+                $pyvenvConfig[$keyval[0]] = $val
+                Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
+            }
+        }
+    }
+    return $pyvenvConfig
+}
+
+
+<# Begin Activate script --------------------------------------------------- #>
+
+# Determine the containing directory of this script
+$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
+$VenvExecDir = Get-Item -Path $VenvExecPath
+
+Write-Verbose "Activation script is located in path: '$VenvExecPath'"
+Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
+Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
+
+# Set values required in priority: CmdLine, ConfigFile, Default
+# First, get the location of the virtual environment, it might not be
+# VenvExecDir if specified on the command line.
+if ($VenvDir) {
+    Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
+} else {
+    Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
+    $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
+    Write-Verbose "VenvDir=$VenvDir"
+}
+
+# Next, read the `pyvenv.cfg` file to determine any required value such
+# as `prompt`.
+$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
+
+# Next, set the prompt from the command line, or the config file, or
+# just use the name of the virtual environment folder.
+if ($Prompt) {
+    Write-Verbose "Prompt specified as argument, using '$Prompt'"
+} else {
+    Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
+    if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
+        Write-Verbose "  Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
+        $Prompt = $pyvenvCfg['prompt'];
+    }
+    else {
+        Write-Verbose "  Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)"
+        Write-Verbose "  Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
+        $Prompt = Split-Path -Path $venvDir -Leaf
+    }
+}
+
+Write-Verbose "Prompt = '$Prompt'"
+Write-Verbose "VenvDir='$VenvDir'"
+
+# Deactivate any currently active virtual environment, but leave the
+# deactivate function in place.
+deactivate -nondestructive
+
+# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
+# that there is an activated venv.
+$env:VIRTUAL_ENV = $VenvDir
+
+if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
+
+    Write-Verbose "Setting prompt to '$Prompt'"
+
+    # Set the prompt to include the env name
+    # Make sure _OLD_VIRTUAL_PROMPT is global
+    function global:_OLD_VIRTUAL_PROMPT { "" }
+    Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
+    New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
+
+    function global:prompt {
+        Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
+        _OLD_VIRTUAL_PROMPT
+    }
+}
+
+# Clear PYTHONHOME
+if (Test-Path -Path Env:PYTHONHOME) {
+    Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
+    Remove-Item -Path Env:PYTHONHOME
+}
+
+# Add the venv to the PATH
+Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
+$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

+ 76 - 0
venv/bin/activate

@@ -0,0 +1,76 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+deactivate () {
+    # reset old environment variables
+    if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
+        PATH="${_OLD_VIRTUAL_PATH:-}"
+        export PATH
+        unset _OLD_VIRTUAL_PATH
+    fi
+    if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
+        PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
+        export PYTHONHOME
+        unset _OLD_VIRTUAL_PYTHONHOME
+    fi
+
+    # This should detect bash and zsh, which have a hash command that must
+    # be called to get it to forget past commands.  Without forgetting
+    # past commands the $PATH changes we made may not be respected
+    if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+        hash -r
+    fi
+
+    if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
+        PS1="${_OLD_VIRTUAL_PS1:-}"
+        export PS1
+        unset _OLD_VIRTUAL_PS1
+    fi
+
+    unset VIRTUAL_ENV
+    if [ ! "${1:-}" = "nondestructive" ] ; then
+    # Self destruct!
+        unset -f deactivate
+    fi
+}
+
+# unset irrelevant variables
+deactivate nondestructive
+
+VIRTUAL_ENV="/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv"
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/bin:$PATH"
+export PATH
+
+# unset PYTHONHOME if set
+# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
+# could use `if (set -u; : $PYTHONHOME) ;` in bash
+if [ -n "${PYTHONHOME:-}" ] ; then
+    _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
+    unset PYTHONHOME
+fi
+
+if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
+    _OLD_VIRTUAL_PS1="${PS1:-}"
+    if [ "x(venv) " != x ] ; then
+	PS1="(venv) ${PS1:-}"
+    else
+    if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
+        # special case for Aspen magic directories
+        # see http://www.zetadev.com/software/aspen/
+        PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
+    else
+        PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
+    fi
+    fi
+    export PS1
+fi
+
+# This should detect bash and zsh, which have a hash command that must
+# be called to get it to forget past commands.  Without forgetting
+# past commands the $PATH changes we made may not be respected
+if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+    hash -r
+fi

+ 37 - 0
venv/bin/activate.csh

@@ -0,0 +1,37 @@
+# This file must be used with "source bin/activate.csh" *from csh*.
+# You cannot run it directly.
+# Created by Davide Di Blasi <davidedb@gmail.com>.
+# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
+
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+setenv VIRTUAL_ENV "/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv"
+
+set _OLD_VIRTUAL_PATH="$PATH"
+setenv PATH "$VIRTUAL_ENV/bin:$PATH"
+
+
+set _OLD_VIRTUAL_PROMPT="$prompt"
+
+if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
+    if ("venv" != "") then
+        set env_name = "venv"
+    else
+        if (`basename "VIRTUAL_ENV"` == "__") then
+            # special case for Aspen magic directories
+            # see http://www.zetadev.com/software/aspen/
+            set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
+        else
+            set env_name = `basename "$VIRTUAL_ENV"`
+        endif
+    endif
+    set prompt = "[$env_name] $prompt"
+    unset env_name
+endif
+
+alias pydoc python -m pydoc
+
+rehash

+ 75 - 0
venv/bin/activate.fish

@@ -0,0 +1,75 @@
+# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
+# you cannot run it directly
+
+function deactivate  -d "Exit virtualenv and return to normal shell environment"
+    # reset old environment variables
+    if test -n "$_OLD_VIRTUAL_PATH"
+        set -gx PATH $_OLD_VIRTUAL_PATH
+        set -e _OLD_VIRTUAL_PATH
+    end
+    if test -n "$_OLD_VIRTUAL_PYTHONHOME"
+        set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+        set -e _OLD_VIRTUAL_PYTHONHOME
+    end
+
+    if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
+        functions -e fish_prompt
+        set -e _OLD_FISH_PROMPT_OVERRIDE
+        functions -c _old_fish_prompt fish_prompt
+        functions -e _old_fish_prompt
+    end
+
+    set -e VIRTUAL_ENV
+    if test "$argv[1]" != "nondestructive"
+        # Self destruct!
+        functions -e deactivate
+    end
+end
+
+# unset irrelevant variables
+deactivate nondestructive
+
+set -gx VIRTUAL_ENV "/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv"
+
+set -gx _OLD_VIRTUAL_PATH $PATH
+set -gx PATH "$VIRTUAL_ENV/bin" $PATH
+
+# unset PYTHONHOME if set
+if set -q PYTHONHOME
+    set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+    set -e PYTHONHOME
+end
+
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+    # fish uses a function instead of an env var to generate the prompt.
+
+    # save the current fish_prompt function as the function _old_fish_prompt
+    functions -c fish_prompt _old_fish_prompt
+
+    # with the original prompt function renamed, we can override with our own.
+    function fish_prompt
+        # Save the return status of the last command
+        set -l old_status $status
+
+        # Prompt override?
+        if test -n "(venv) "
+            printf "%s%s" "(venv) " (set_color normal)
+        else
+            # ...Otherwise, prepend env
+            set -l _checkbase (basename "$VIRTUAL_ENV")
+            if test $_checkbase = "__"
+                # special case for Aspen magic directories
+                # see http://www.zetadev.com/software/aspen/
+                printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
+            else
+                printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
+            end
+        end
+
+        # Restore the return status of the previous command.
+        echo "exit $old_status" | .
+        _old_fish_prompt
+    end
+
+    set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
+end

+ 8 - 0
venv/bin/easy_install

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from setuptools.command.easy_install import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/easy_install-3.8

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from setuptools.command.easy_install import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/f2py

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from numpy.f2py.f2py2e import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/f2py3

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from numpy.f2py.f2py2e import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/f2py3.8

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from numpy.f2py.f2py2e import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/pip

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/pip3

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/pip3.8

@@ -0,0 +1,8 @@
+#!/home/songzihuan/文档/PyProject/物理/弹簧加速度/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 1 - 0
venv/bin/python

@@ -0,0 +1 @@
+python3.8

+ 1 - 0
venv/bin/python3

@@ -0,0 +1 @@
+python3.8

+ 1 - 0
venv/bin/python3.8

@@ -0,0 +1 @@
+/usr/bin/python3.8

+ 3 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/DESCRIPTION.rst

@@ -0,0 +1,3 @@
+UNKNOWN
+
+

+ 1 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 25 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/METADATA

@@ -0,0 +1,25 @@
+Metadata-Version: 2.0
+Name: cycler
+Version: 0.10.0
+Summary: Composable style cycles
+Home-page: http://github.com/matplotlib/cycler
+Author: Thomas A Caswell
+Author-email: matplotlib-users@python.org
+License: BSD
+Keywords: cycle kwargs
+Platform: Cross platform (Linux
+Platform: Mac OSX
+Platform: Windows)
+Classifier: Development Status :: 4 - Beta
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Requires-Dist: six
+
+UNKNOWN
+
+

+ 9 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/RECORD

@@ -0,0 +1,9 @@
+__pycache__/cycler.cpython-38.pyc,,
+cycler-0.10.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
+cycler-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+cycler-0.10.0.dist-info/METADATA,sha256=aWX1pyo7D2hSDNZ2Q6Zl7DxhUQdpyu1O5uNABnvz000,722
+cycler-0.10.0.dist-info/RECORD,,
+cycler-0.10.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
+cycler-0.10.0.dist-info/metadata.json,sha256=CCBpg-KQU-VRL1unJcHPWKQeQbB84G0j7-BeCj7YUbU,875
+cycler-0.10.0.dist-info/top_level.txt,sha256=D8BVVDdAAelLb2FOEz7lDpc6-AL21ylKPrMhtG6yzyE,7
+cycler.py,sha256=ed3G39unvVEBrBZVDwnE0FFroRNsOLkbJ_TwIT5CjCU,15959

+ 6 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/WHEEL

@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.29.0)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+

+ 1 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/metadata.json

@@ -0,0 +1 @@
+{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "matplotlib-users@python.org", "name": "Thomas A Caswell", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/matplotlib/cycler"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "keywords": ["cycle", "kwargs"], "license": "BSD", "metadata_version": "2.0", "name": "cycler", "platform": "Cross platform (Linux", "run_requires": [{"requires": ["six"]}], "summary": "Composable style cycles", "version": "0.10.0"}

+ 1 - 0
venv/lib/python3.8/site-packages/cycler-0.10.0.dist-info/top_level.txt

@@ -0,0 +1 @@
+cycler

+ 558 - 0
venv/lib/python3.8/site-packages/cycler.py

@@ -0,0 +1,558 @@
+"""
+Cycler
+======
+
+Cycling through combinations of values, producing dictionaries.
+
+You can add cyclers::
+
+    from cycler import cycler
+    cc = (cycler(color=list('rgb')) +
+          cycler(linestyle=['-', '--', '-.']))
+    for d in cc:
+        print(d)
+
+Results in::
+
+    {'color': 'r', 'linestyle': '-'}
+    {'color': 'g', 'linestyle': '--'}
+    {'color': 'b', 'linestyle': '-.'}
+
+
+You can multiply cyclers::
+
+    from cycler import cycler
+    cc = (cycler(color=list('rgb')) *
+          cycler(linestyle=['-', '--', '-.']))
+    for d in cc:
+        print(d)
+
+Results in::
+
+    {'color': 'r', 'linestyle': '-'}
+    {'color': 'r', 'linestyle': '--'}
+    {'color': 'r', 'linestyle': '-.'}
+    {'color': 'g', 'linestyle': '-'}
+    {'color': 'g', 'linestyle': '--'}
+    {'color': 'g', 'linestyle': '-.'}
+    {'color': 'b', 'linestyle': '-'}
+    {'color': 'b', 'linestyle': '--'}
+    {'color': 'b', 'linestyle': '-.'}
+"""
+
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+import six
+from itertools import product, cycle
+from six.moves import zip, reduce
+from operator import mul, add
+import copy
+
+__version__ = '0.10.0'
+
+
+def _process_keys(left, right):
+    """
+    Helper function to compose cycler keys
+
+    Parameters
+    ----------
+    left, right : iterable of dictionaries or None
+        The cyclers to be composed
+    Returns
+    -------
+    keys : set
+        The keys in the composition of the two cyclers
+    """
+    l_peek = next(iter(left)) if left is not None else {}
+    r_peek = next(iter(right)) if right is not None else {}
+    l_key = set(l_peek.keys())
+    r_key = set(r_peek.keys())
+    if l_key & r_key:
+        raise ValueError("Can not compose overlapping cycles")
+    return l_key | r_key
+
+
+class Cycler(object):
+    """
+    Composable cycles
+
+    This class has compositions methods:
+
+    ``+``
+      for 'inner' products (zip)
+
+    ``+=``
+      in-place ``+``
+
+    ``*``
+      for outer products (itertools.product) and integer multiplication
+
+    ``*=``
+      in-place ``*``
+
+    and supports basic slicing via ``[]``
+
+    Parameters
+    ----------
+    left : Cycler or None
+        The 'left' cycler
+
+    right : Cycler or None
+        The 'right' cycler
+
+    op : func or None
+        Function which composes the 'left' and 'right' cyclers.
+
+    """
+    def __call__(self):
+        return cycle(self)
+
+    def __init__(self, left, right=None, op=None):
+        """Semi-private init
+
+        Do not use this directly, use `cycler` function instead.
+        """
+        if isinstance(left, Cycler):
+            self._left = Cycler(left._left, left._right, left._op)
+        elif left is not None:
+            # Need to copy the dictionary or else that will be a residual
+            # mutable that could lead to strange errors
+            self._left = [copy.copy(v) for v in left]
+        else:
+            self._left = None
+
+        if isinstance(right, Cycler):
+            self._right = Cycler(right._left, right._right, right._op)
+        elif right is not None:
+            # Need to copy the dictionary or else that will be a residual
+            # mutable that could lead to strange errors
+            self._right = [copy.copy(v) for v in right]
+        else:
+            self._right = None
+
+        self._keys = _process_keys(self._left, self._right)
+        self._op = op
+
+    @property
+    def keys(self):
+        """
+        The keys this Cycler knows about
+        """
+        return set(self._keys)
+
+    def change_key(self, old, new):
+        """
+        Change a key in this cycler to a new name.
+        Modification is performed in-place.
+
+        Does nothing if the old key is the same as the new key.
+        Raises a ValueError if the new key is already a key.
+        Raises a KeyError if the old key isn't a key.
+
+        """
+        if old == new:
+            return
+        if new in self._keys:
+            raise ValueError("Can't replace %s with %s, %s is already a key" %
+                             (old, new, new))
+        if old not in self._keys:
+            raise KeyError("Can't replace %s with %s, %s is not a key" %
+                           (old, new, old))
+
+        self._keys.remove(old)
+        self._keys.add(new)
+
+        if self._right is not None and old in self._right.keys:
+            self._right.change_key(old, new)
+
+        # self._left should always be non-None
+        # if self._keys is non-empty.
+        elif isinstance(self._left, Cycler):
+            self._left.change_key(old, new)
+        else:
+            # It should be completely safe at this point to
+            # assume that the old key can be found in each
+            # iteration.
+            self._left = [{new: entry[old]} for entry in self._left]
+
+    def _compose(self):
+        """
+        Compose the 'left' and 'right' components of this cycle
+        with the proper operation (zip or product as of now)
+        """
+        for a, b in self._op(self._left, self._right):
+            out = dict()
+            out.update(a)
+            out.update(b)
+            yield out
+
+    @classmethod
+    def _from_iter(cls, label, itr):
+        """
+        Class method to create 'base' Cycler objects
+        that do not have a 'right' or 'op' and for which
+        the 'left' object is not another Cycler.
+
+        Parameters
+        ----------
+        label : str
+            The property key.
+
+        itr : iterable
+            Finite length iterable of the property values.
+
+        Returns
+        -------
+        cycler : Cycler
+            New 'base' `Cycler`
+        """
+        ret = cls(None)
+        ret._left = list({label: v} for v in itr)
+        ret._keys = set([label])
+        return ret
+
+    def __getitem__(self, key):
+        # TODO : maybe add numpy style fancy slicing
+        if isinstance(key, slice):
+            trans = self.by_key()
+            return reduce(add, (_cycler(k, v[key])
+                                for k, v in six.iteritems(trans)))
+        else:
+            raise ValueError("Can only use slices with Cycler.__getitem__")
+
+    def __iter__(self):
+        if self._right is None:
+            return iter(dict(l) for l in self._left)
+
+        return self._compose()
+
+    def __add__(self, other):
+        """
+        Pair-wise combine two equal length cycles (zip)
+
+        Parameters
+        ----------
+        other : Cycler
+           The second Cycler
+        """
+        if len(self) != len(other):
+            raise ValueError("Can only add equal length cycles, "
+                             "not {0} and {1}".format(len(self), len(other)))
+        return Cycler(self, other, zip)
+
+    def __mul__(self, other):
+        """
+        Outer product of two cycles (`itertools.product`) or integer
+        multiplication.
+
+        Parameters
+        ----------
+        other : Cycler or int
+           The second Cycler or integer
+        """
+        if isinstance(other, Cycler):
+            return Cycler(self, other, product)
+        elif isinstance(other, int):
+            trans = self.by_key()
+            return reduce(add, (_cycler(k, v*other)
+                                for k, v in six.iteritems(trans)))
+        else:
+            return NotImplemented
+
+    def __rmul__(self, other):
+        return self * other
+
+    def __len__(self):
+        op_dict = {zip: min, product: mul}
+        if self._right is None:
+            return len(self._left)
+        l_len = len(self._left)
+        r_len = len(self._right)
+        return op_dict[self._op](l_len, r_len)
+
+    def __iadd__(self, other):
+        """
+        In-place pair-wise combine two equal length cycles (zip)
+
+        Parameters
+        ----------
+        other : Cycler
+           The second Cycler
+        """
+        if not isinstance(other, Cycler):
+            raise TypeError("Cannot += with a non-Cycler object")
+        # True shallow copy of self is fine since this is in-place
+        old_self = copy.copy(self)
+        self._keys = _process_keys(old_self, other)
+        self._left = old_self
+        self._op = zip
+        self._right = Cycler(other._left, other._right, other._op)
+        return self
+
+    def __imul__(self, other):
+        """
+        In-place outer product of two cycles (`itertools.product`)
+
+        Parameters
+        ----------
+        other : Cycler
+           The second Cycler
+        """
+        if not isinstance(other, Cycler):
+            raise TypeError("Cannot *= with a non-Cycler object")
+        # True shallow copy of self is fine since this is in-place
+        old_self = copy.copy(self)
+        self._keys = _process_keys(old_self, other)
+        self._left = old_self
+        self._op = product
+        self._right = Cycler(other._left, other._right, other._op)
+        return self
+
+    def __eq__(self, other):
+        """
+        Check equality
+        """
+        if len(self) != len(other):
+            return False
+        if self.keys ^ other.keys:
+            return False
+
+        return all(a == b for a, b in zip(self, other))
+
+    def __repr__(self):
+        op_map = {zip: '+', product: '*'}
+        if self._right is None:
+            lab = self.keys.pop()
+            itr = list(v[lab] for v in self)
+            return "cycler({lab!r}, {itr!r})".format(lab=lab, itr=itr)
+        else:
+            op = op_map.get(self._op, '?')
+            msg = "({left!r} {op} {right!r})"
+            return msg.format(left=self._left, op=op, right=self._right)
+
+    def _repr_html_(self):
+        # an table showing the value of each key through a full cycle
+        output = "<table>"
+        sorted_keys = sorted(self.keys, key=repr)
+        for key in sorted_keys:
+            output += "<th>{key!r}</th>".format(key=key)
+        for d in iter(self):
+            output += "<tr>"
+            for k in sorted_keys:
+                output += "<td>{val!r}</td>".format(val=d[k])
+            output += "</tr>"
+        output += "</table>"
+        return output
+
+    def by_key(self):
+        """Values by key
+
+        This returns the transposed values of the cycler.  Iterating
+        over a `Cycler` yields dicts with a single value for each key,
+        this method returns a `dict` of `list` which are the values
+        for the given key.
+
+        The returned value can be used to create an equivalent `Cycler`
+        using only `+`.
+
+        Returns
+        -------
+        transpose : dict
+            dict of lists of the values for each key.
+        """
+
+        # TODO : sort out if this is a bottle neck, if there is a better way
+        # and if we care.
+
+        keys = self.keys
+        # change this to dict comprehension when drop 2.6
+        out = dict((k,  list()) for k in keys)
+
+        for d in self:
+            for k in keys:
+                out[k].append(d[k])
+        return out
+
+    # for back compatibility
+    _transpose = by_key
+
+    def simplify(self):
+        """Simplify the Cycler
+
+        Returned as a composition using only sums (no multiplications)
+
+        Returns
+        -------
+        simple : Cycler
+            An equivalent cycler using only summation"""
+        # TODO: sort out if it is worth the effort to make sure this is
+        # balanced.  Currently it is is
+        # (((a + b) + c) + d) vs
+        # ((a + b) + (c + d))
+        # I would believe that there is some performance implications
+
+        trans = self.by_key()
+        return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans)))
+
+    def concat(self, other):
+        """Concatenate this cycler and an other.
+
+        The keys must match exactly.
+
+        This returns a single Cycler which is equivalent to
+        `itertools.chain(self, other)`
+
+        Examples
+        --------
+
+        >>> num = cycler('a', range(3))
+        >>> let = cycler('a', 'abc')
+        >>> num.concat(let)
+        cycler('a', [0, 1, 2, 'a', 'b', 'c'])
+
+        Parameters
+        ----------
+        other : `Cycler`
+            The `Cycler` to concatenate to this one.
+
+        Returns
+        -------
+        ret : `Cycler`
+            The concatenated `Cycler`
+        """
+        return concat(self, other)
+
+
+def concat(left, right):
+    """Concatenate two cyclers.
+
+    The keys must match exactly.
+
+    This returns a single Cycler which is equivalent to
+    `itertools.chain(left, right)`
+
+    Examples
+    --------
+
+    >>> num = cycler('a', range(3))
+    >>> let = cycler('a', 'abc')
+    >>> num.concat(let)
+    cycler('a', [0, 1, 2, 'a', 'b', 'c'])
+
+    Parameters
+    ----------
+    left, right : `Cycler`
+        The two `Cycler` instances to concatenate
+
+    Returns
+    -------
+    ret : `Cycler`
+        The concatenated `Cycler`
+    """
+    if left.keys != right.keys:
+        msg = '\n\t'.join(["Keys do not match:",
+                           "Intersection: {both!r}",
+                           "Disjoint: {just_one!r}"]).format(
+                               both=left.keys & right.keys,
+                               just_one=left.keys ^ right.keys)
+
+        raise ValueError(msg)
+
+    _l = left.by_key()
+    _r = right.by_key()
+    return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
+
+
+def cycler(*args, **kwargs):
+    """
+    Create a new `Cycler` object from a single positional argument,
+    a pair of positional arguments, or the combination of keyword arguments.
+
+    cycler(arg)
+    cycler(label1=itr1[, label2=iter2[, ...]])
+    cycler(label, itr)
+
+    Form 1 simply copies a given `Cycler` object.
+
+    Form 2 composes a `Cycler` as an inner product of the
+    pairs of keyword arguments. In other words, all of the
+    iterables are cycled simultaneously, as if through zip().
+
+    Form 3 creates a `Cycler` from a label and an iterable.
+    This is useful for when the label cannot be a keyword argument
+    (e.g., an integer or a name that has a space in it).
+
+    Parameters
+    ----------
+    arg : Cycler
+        Copy constructor for Cycler (does a shallow copy of iterables).
+
+    label : name
+        The property key. In the 2-arg form of the function,
+        the label can be any hashable object. In the keyword argument
+        form of the function, it must be a valid python identifier.
+
+    itr : iterable
+        Finite length iterable of the property values.
+        Can be a single-property `Cycler` that would
+        be like a key change, but as a shallow copy.
+
+    Returns
+    -------
+    cycler : Cycler
+        New `Cycler` for the given property
+
+    """
+    if args and kwargs:
+        raise TypeError("cyl() can only accept positional OR keyword "
+                        "arguments -- not both.")
+
+    if len(args) == 1:
+        if not isinstance(args[0], Cycler):
+            raise TypeError("If only one positional argument given, it must "
+                            " be a Cycler instance.")
+        return Cycler(args[0])
+    elif len(args) == 2:
+        return _cycler(*args)
+    elif len(args) > 2:
+        raise TypeError("Only a single Cycler can be accepted as the lone "
+                        "positional argument. Use keyword arguments instead.")
+
+    if kwargs:
+        return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))
+
+    raise TypeError("Must have at least a positional OR keyword arguments")
+
+
+def _cycler(label, itr):
+    """
+    Create a new `Cycler` object from a property name and
+    iterable of values.
+
+    Parameters
+    ----------
+    label : hashable
+        The property key.
+
+    itr : iterable
+        Finite length iterable of the property values.
+
+    Returns
+    -------
+    cycler : Cycler
+        New `Cycler` for the given property
+    """
+    if isinstance(itr, Cycler):
+        keys = itr.keys
+        if len(keys) != 1:
+            msg = "Can not create Cycler from a multi-property Cycler"
+            raise ValueError(msg)
+
+        lab = keys.pop()
+        # Doesn't need to be a new list because
+        # _from_iter() will be creating that new list anyway.
+        itr = (v[lab] for v in itr)
+
+    return Cycler._from_iter(label, itr)

+ 8 - 0
venv/lib/python3.8/site-packages/dateutil/__init__.py

@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+try:
+    from ._version import version as __version__
+except ImportError:
+    __version__ = 'unknown'
+
+__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
+           'utils', 'zoneinfo']

+ 43 - 0
venv/lib/python3.8/site-packages/dateutil/_common.py

@@ -0,0 +1,43 @@
+"""
+Common code used in multiple modules.
+"""
+
+
+class weekday(object):
+    __slots__ = ["weekday", "n"]
+
+    def __init__(self, weekday, n=None):
+        self.weekday = weekday
+        self.n = n
+
+    def __call__(self, n):
+        if n == self.n:
+            return self
+        else:
+            return self.__class__(self.weekday, n)
+
+    def __eq__(self, other):
+        try:
+            if self.weekday != other.weekday or self.n != other.n:
+                return False
+        except AttributeError:
+            return False
+        return True
+
+    def __hash__(self):
+        return hash((
+          self.weekday,
+          self.n,
+        ))
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
+        if not self.n:
+            return s
+        else:
+            return "%s(%+d)" % (s, self.n)
+
+# vim:ts=4:sw=4:et

+ 4 - 0
venv/lib/python3.8/site-packages/dateutil/_version.py

@@ -0,0 +1,4 @@
+# coding: utf-8
+# file generated by setuptools_scm
+# don't change, don't track in version control
+version = '2.8.1'

+ 89 - 0
venv/lib/python3.8/site-packages/dateutil/easter.py

@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers a generic easter computing method for any given year, using
+Western, Orthodox or Julian algorithms.
+"""
+
+import datetime
+
+__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
+
+EASTER_JULIAN = 1
+EASTER_ORTHODOX = 2
+EASTER_WESTERN = 3
+
+
+def easter(year, method=EASTER_WESTERN):
+    """
+    This method was ported from the work done by GM Arts,
+    on top of the algorithm by Claus Tondering, which was
+    based in part on the algorithm of Ouding (1940), as
+    quoted in "Explanatory Supplement to the Astronomical
+    Almanac", P.  Kenneth Seidelmann, editor.
+
+    This algorithm implements three different easter
+    calculation methods:
+
+    1 - Original calculation in Julian calendar, valid in
+        dates after 326 AD
+    2 - Original method, with date converted to Gregorian
+        calendar, valid in years 1583 to 4099
+    3 - Revised method, in Gregorian calendar, valid in
+        years 1583 to 4099 as well
+
+    These methods are represented by the constants:
+
+    * ``EASTER_JULIAN   = 1``
+    * ``EASTER_ORTHODOX = 2``
+    * ``EASTER_WESTERN  = 3``
+
+    The default method is method 3.
+
+    More about the algorithm may be found at:
+
+    `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_
+
+    and
+
+    `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_
+
+    """
+
+    if not (1 <= method <= 3):
+        raise ValueError("invalid method")
+
+    # g - Golden year - 1
+    # c - Century
+    # h - (23 - Epact) mod 30
+    # i - Number of days from March 21 to Paschal Full Moon
+    # j - Weekday for PFM (0=Sunday, etc)
+    # p - Number of days from March 21 to Sunday on or before PFM
+    #     (-6 to 28 methods 1 & 3, to 56 for method 2)
+    # e - Extra days to add for method 2 (converting Julian
+    #     date to Gregorian date)
+
+    y = year
+    g = y % 19
+    e = 0
+    if method < 3:
+        # Old method
+        i = (19*g + 15) % 30
+        j = (y + y//4 + i) % 7
+        if method == 2:
+            # Extra dates to convert Julian to Gregorian date
+            e = 10
+            if y > 1600:
+                e = e + y//100 - 16 - (y//100 - 16)//4
+    else:
+        # New method
+        c = y//100
+        h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
+        i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
+        j = (y + y//4 + i + 2 - c + c//4) % 7
+
+    # p can be from -6 to 56 corresponding to dates 22 March to 23 May
+    # (later dates apply to method 2, although 23 May never actually occurs)
+    p = i - j + e
+    d = 1 + (p + 27 + (p + 6)//40) % 31
+    m = 3 + (p + 26)//30
+    return datetime.date(int(y), int(m), int(d))

+ 61 - 0
venv/lib/python3.8/site-packages/dateutil/parser/__init__.py

@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+from ._parser import parse, parser, parserinfo, ParserError
+from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
+from ._parser import UnknownTimezoneWarning
+
+from ._parser import __doc__
+
+from .isoparser import isoparser, isoparse
+
+__all__ = ['parse', 'parser', 'parserinfo',
+           'isoparse', 'isoparser',
+           'ParserError',
+           'UnknownTimezoneWarning']
+
+
+###
+# Deprecate portions of the private interface so that downstream code that
+# is improperly relying on it is given *some* notice.
+
+
+def __deprecated_private_func(f):
+    from functools import wraps
+    import warnings
+
+    msg = ('{name} is a private function and may break without warning, '
+           'it will be moved and or renamed in future versions.')
+    msg = msg.format(name=f.__name__)
+
+    @wraps(f)
+    def deprecated_func(*args, **kwargs):
+        warnings.warn(msg, DeprecationWarning)
+        return f(*args, **kwargs)
+
+    return deprecated_func
+
+def __deprecate_private_class(c):
+    import warnings
+
+    msg = ('{name} is a private class and may break without warning, '
+           'it will be moved and or renamed in future versions.')
+    msg = msg.format(name=c.__name__)
+
+    class private_class(c):
+        __doc__ = c.__doc__
+
+        def __init__(self, *args, **kwargs):
+            warnings.warn(msg, DeprecationWarning)
+            super(private_class, self).__init__(*args, **kwargs)
+
+    private_class.__name__ = c.__name__
+
+    return private_class
+
+
+from ._parser import _timelex, _resultbase
+from ._parser import _tzparser, _parsetz
+
+_timelex = __deprecate_private_class(_timelex)
+_tzparser = __deprecate_private_class(_tzparser)
+_resultbase = __deprecate_private_class(_resultbase)
+_parsetz = __deprecated_private_func(_parsetz)

+ 1609 - 0
venv/lib/python3.8/site-packages/dateutil/parser/_parser.py

@@ -0,0 +1,1609 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers a generic date/time string parser which is able to parse
+most known formats to represent a date and/or time.
+
+This module attempts to be forgiving with regards to unlikely input formats,
+returning a datetime object even for dates which are ambiguous. If an element
+of a date/time stamp is omitted, the following rules are applied:
+
+- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour
+  on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is
+  specified.
+- If a time zone is omitted, a timezone-naive datetime is returned.
+
+If any other elements are missing, they are taken from the
+:class:`datetime.datetime` object passed to the parameter ``default``. If this
+results in a day number exceeding the valid number of days per month, the
+value falls back to the end of the month.
+
+Additional resources about date/time string formats can be found below:
+
+- `A summary of the international standard date and time notation
+  <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
+- `W3C Date and Time Formats <http://www.w3.org/TR/NOTE-datetime>`_
+- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_
+- `CPAN ParseDate module
+  <http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
+- `Java SimpleDateFormat Class
+  <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
+"""
+from __future__ import unicode_literals
+
+import datetime
+import re
+import string
+import time
+import warnings
+
+from calendar import monthrange
+from io import StringIO
+
+import six
+from six import integer_types, text_type
+
+from decimal import Decimal
+
+from warnings import warn
+
+from .. import relativedelta
+from .. import tz
+
+__all__ = ["parse", "parserinfo", "ParserError"]
+
+
+# TODO: pandas.core.tools.datetimes imports this explicitly.  Might be worth
+# making public and/or figuring out if there is something we can
+# take off their plate.
+class _timelex(object):
+    # Fractional seconds are sometimes split by a comma
+    _split_decimal = re.compile("([.,])")
+
+    def __init__(self, instream):
+        if six.PY2:
+            # In Python 2, we can't duck type properly because unicode has
+            # a 'decode' function, and we'd be double-decoding
+            if isinstance(instream, (bytes, bytearray)):
+                instream = instream.decode()
+        else:
+            if getattr(instream, 'decode', None) is not None:
+                instream = instream.decode()
+
+        if isinstance(instream, text_type):
+            instream = StringIO(instream)
+        elif getattr(instream, 'read', None) is None:
+            raise TypeError('Parser must be a string or character stream, not '
+                            '{itype}'.format(itype=instream.__class__.__name__))
+
+        self.instream = instream
+        self.charstack = []
+        self.tokenstack = []
+        self.eof = False
+
+    def get_token(self):
+        """
+        This function breaks the time string into lexical units (tokens), which
+        can be parsed by the parser. Lexical units are demarcated by changes in
+        the character set, so any continuous string of letters is considered
+        one unit, any continuous string of numbers is considered one unit.
+
+        The main complication arises from the fact that dots ('.') can be used
+        both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
+        "4:30:21.447"). As such, it is necessary to read the full context of
+        any dot-separated strings before breaking it into tokens; as such, this
+        function maintains a "token stack", for when the ambiguous context
+        demands that multiple tokens be parsed at once.
+        """
+        if self.tokenstack:
+            return self.tokenstack.pop(0)
+
+        seenletters = False
+        token = None
+        state = None
+
+        while not self.eof:
+            # We only realize that we've reached the end of a token when we
+            # find a character that's not part of the current token - since
+            # that character may be part of the next token, it's stored in the
+            # charstack.
+            if self.charstack:
+                nextchar = self.charstack.pop(0)
+            else:
+                nextchar = self.instream.read(1)
+                while nextchar == '\x00':
+                    nextchar = self.instream.read(1)
+
+            if not nextchar:
+                self.eof = True
+                break
+            elif not state:
+                # First character of the token - determines if we're starting
+                # to parse a word, a number or something else.
+                token = nextchar
+                if self.isword(nextchar):
+                    state = 'a'
+                elif self.isnum(nextchar):
+                    state = '0'
+                elif self.isspace(nextchar):
+                    token = ' '
+                    break  # emit token
+                else:
+                    break  # emit token
+            elif state == 'a':
+                # If we've already started reading a word, we keep reading
+                # letters until we find something that's not part of a word.
+                seenletters = True
+                if self.isword(nextchar):
+                    token += nextchar
+                elif nextchar == '.':
+                    token += nextchar
+                    state = 'a.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+            elif state == '0':
+                # If we've already started reading a number, we keep reading
+                # numbers until we find something that doesn't fit.
+                if self.isnum(nextchar):
+                    token += nextchar
+                elif nextchar == '.' or (nextchar == ',' and len(token) >= 2):
+                    token += nextchar
+                    state = '0.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+            elif state == 'a.':
+                # If we've seen some letters and a dot separator, continue
+                # parsing, and the tokens will be broken up later.
+                seenletters = True
+                if nextchar == '.' or self.isword(nextchar):
+                    token += nextchar
+                elif self.isnum(nextchar) and token[-1] == '.':
+                    token += nextchar
+                    state = '0.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+            elif state == '0.':
+                # If we've seen at least one dot separator, keep going, we'll
+                # break up the tokens later.
+                if nextchar == '.' or self.isnum(nextchar):
+                    token += nextchar
+                elif self.isword(nextchar) and token[-1] == '.':
+                    token += nextchar
+                    state = 'a.'
+                else:
+                    self.charstack.append(nextchar)
+                    break  # emit token
+
+        if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
+                                       token[-1] in '.,')):
+            l = self._split_decimal.split(token)
+            token = l[0]
+            for tok in l[1:]:
+                if tok:
+                    self.tokenstack.append(tok)
+
+        if state == '0.' and token.count('.') == 0:
+            token = token.replace(',', '.')
+
+        return token
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        token = self.get_token()
+        if token is None:
+            raise StopIteration
+
+        return token
+
+    def next(self):
+        return self.__next__()  # Python 2.x support
+
+    @classmethod
+    def split(cls, s):
+        return list(cls(s))
+
+    @classmethod
+    def isword(cls, nextchar):
+        """ Whether or not the next character is part of a word """
+        return nextchar.isalpha()
+
+    @classmethod
+    def isnum(cls, nextchar):
+        """ Whether the next character is part of a number """
+        return nextchar.isdigit()
+
+    @classmethod
+    def isspace(cls, nextchar):
+        """ Whether the next character is whitespace """
+        return nextchar.isspace()
+
+
+class _resultbase(object):
+
+    def __init__(self):
+        for attr in self.__slots__:
+            setattr(self, attr, None)
+
+    def _repr(self, classname):
+        l = []
+        for attr in self.__slots__:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("%s=%s" % (attr, repr(value)))
+        return "%s(%s)" % (classname, ", ".join(l))
+
+    def __len__(self):
+        return (sum(getattr(self, attr) is not None
+                    for attr in self.__slots__))
+
+    def __repr__(self):
+        return self._repr(self.__class__.__name__)
+
+
+class parserinfo(object):
+    """
+    Class which handles what inputs are accepted. Subclass this to customize
+    the language and acceptable values for each parameter.
+
+    :param dayfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the day (``True``) or month (``False``). If
+        ``yearfirst`` is set to ``True``, this distinguishes between YDM
+        and YMD. Default is ``False``.
+
+    :param yearfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the year. If ``True``, the first number is taken
+        to be the year, otherwise the last number is taken to be the year.
+        Default is ``False``.
+    """
+
+    # m from a.m/p.m, t from ISO T separator
+    JUMP = [" ", ".", ",", ";", "-", "/", "'",
+            "at", "on", "and", "ad", "m", "t", "of",
+            "st", "nd", "rd", "th"]
+
+    WEEKDAYS = [("Mon", "Monday"),
+                ("Tue", "Tuesday"),     # TODO: "Tues"
+                ("Wed", "Wednesday"),
+                ("Thu", "Thursday"),    # TODO: "Thurs"
+                ("Fri", "Friday"),
+                ("Sat", "Saturday"),
+                ("Sun", "Sunday")]
+    MONTHS = [("Jan", "January"),
+              ("Feb", "February"),      # TODO: "Febr"
+              ("Mar", "March"),
+              ("Apr", "April"),
+              ("May", "May"),
+              ("Jun", "June"),
+              ("Jul", "July"),
+              ("Aug", "August"),
+              ("Sep", "Sept", "September"),
+              ("Oct", "October"),
+              ("Nov", "November"),
+              ("Dec", "December")]
+    HMS = [("h", "hour", "hours"),
+           ("m", "minute", "minutes"),
+           ("s", "second", "seconds")]
+    AMPM = [("am", "a"),
+            ("pm", "p")]
+    UTCZONE = ["UTC", "GMT", "Z", "z"]
+    PERTAIN = ["of"]
+    TZOFFSET = {}
+    # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate",
+    #              "Anno Domini", "Year of Our Lord"]
+
+    def __init__(self, dayfirst=False, yearfirst=False):
+        self._jump = self._convert(self.JUMP)
+        self._weekdays = self._convert(self.WEEKDAYS)
+        self._months = self._convert(self.MONTHS)
+        self._hms = self._convert(self.HMS)
+        self._ampm = self._convert(self.AMPM)
+        self._utczone = self._convert(self.UTCZONE)
+        self._pertain = self._convert(self.PERTAIN)
+
+        self.dayfirst = dayfirst
+        self.yearfirst = yearfirst
+
+        self._year = time.localtime().tm_year
+        self._century = self._year // 100 * 100
+
+    def _convert(self, lst):
+        dct = {}
+        for i, v in enumerate(lst):
+            if isinstance(v, tuple):
+                for v in v:
+                    dct[v.lower()] = i
+            else:
+                dct[v.lower()] = i
+        return dct
+
+    def jump(self, name):
+        return name.lower() in self._jump
+
+    def weekday(self, name):
+        try:
+            return self._weekdays[name.lower()]
+        except KeyError:
+            pass
+        return None
+
+    def month(self, name):
+        try:
+            return self._months[name.lower()] + 1
+        except KeyError:
+            pass
+        return None
+
+    def hms(self, name):
+        try:
+            return self._hms[name.lower()]
+        except KeyError:
+            return None
+
+    def ampm(self, name):
+        try:
+            return self._ampm[name.lower()]
+        except KeyError:
+            return None
+
+    def pertain(self, name):
+        return name.lower() in self._pertain
+
+    def utczone(self, name):
+        return name.lower() in self._utczone
+
+    def tzoffset(self, name):
+        if name in self._utczone:
+            return 0
+
+        return self.TZOFFSET.get(name)
+
+    def convertyear(self, year, century_specified=False):
+        """
+        Converts two-digit years to year within [-50, 49]
+        range of self._year (current local time)
+        """
+
+        # Function contract is that the year is always positive
+        assert year >= 0
+
+        if year < 100 and not century_specified:
+            # assume current century to start
+            year += self._century
+
+            if year >= self._year + 50:  # if too far in future
+                year -= 100
+            elif year < self._year - 50:  # if too far in past
+                year += 100
+
+        return year
+
+    def validate(self, res):
+        # move to info
+        if res.year is not None:
+            res.year = self.convertyear(res.year, res.century_specified)
+
+        if ((res.tzoffset == 0 and not res.tzname) or
+             (res.tzname == 'Z' or res.tzname == 'z')):
+            res.tzname = "UTC"
+            res.tzoffset = 0
+        elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
+            res.tzoffset = 0
+        return True
+
+
+class _ymd(list):
+    def __init__(self, *args, **kwargs):
+        super(self.__class__, self).__init__(*args, **kwargs)
+        self.century_specified = False
+        self.dstridx = None
+        self.mstridx = None
+        self.ystridx = None
+
+    @property
+    def has_year(self):
+        return self.ystridx is not None
+
+    @property
+    def has_month(self):
+        return self.mstridx is not None
+
+    @property
+    def has_day(self):
+        return self.dstridx is not None
+
+    def could_be_day(self, value):
+        if self.has_day:
+            return False
+        elif not self.has_month:
+            return 1 <= value <= 31
+        elif not self.has_year:
+            # Be permissive, assume leap year
+            month = self[self.mstridx]
+            return 1 <= value <= monthrange(2000, month)[1]
+        else:
+            month = self[self.mstridx]
+            year = self[self.ystridx]
+            return 1 <= value <= monthrange(year, month)[1]
+
+    def append(self, val, label=None):
+        if hasattr(val, '__len__'):
+            if val.isdigit() and len(val) > 2:
+                self.century_specified = True
+                if label not in [None, 'Y']:  # pragma: no cover
+                    raise ValueError(label)
+                label = 'Y'
+        elif val > 100:
+            self.century_specified = True
+            if label not in [None, 'Y']:  # pragma: no cover
+                raise ValueError(label)
+            label = 'Y'
+
+        super(self.__class__, self).append(int(val))
+
+        if label == 'M':
+            if self.has_month:
+                raise ValueError('Month is already set')
+            self.mstridx = len(self) - 1
+        elif label == 'D':
+            if self.has_day:
+                raise ValueError('Day is already set')
+            self.dstridx = len(self) - 1
+        elif label == 'Y':
+            if self.has_year:
+                raise ValueError('Year is already set')
+            self.ystridx = len(self) - 1
+
+    def _resolve_from_stridxs(self, strids):
+        """
+        Try to resolve the identities of year/month/day elements using
+        ystridx, mstridx, and dstridx, if enough of these are specified.
+        """
+        if len(self) == 3 and len(strids) == 2:
+            # we can back out the remaining stridx value
+            missing = [x for x in range(3) if x not in strids.values()]
+            key = [x for x in ['y', 'm', 'd'] if x not in strids]
+            assert len(missing) == len(key) == 1
+            key = key[0]
+            val = missing[0]
+            strids[key] = val
+
+        assert len(self) == len(strids)  # otherwise this should not be called
+        out = {key: self[strids[key]] for key in strids}
+        return (out.get('y'), out.get('m'), out.get('d'))
+
+    def resolve_ymd(self, yearfirst, dayfirst):
+        len_ymd = len(self)
+        year, month, day = (None, None, None)
+
+        strids = (('y', self.ystridx),
+                  ('m', self.mstridx),
+                  ('d', self.dstridx))
+
+        strids = {key: val for key, val in strids if val is not None}
+        if (len(self) == len(strids) > 0 or
+                (len(self) == 3 and len(strids) == 2)):
+            return self._resolve_from_stridxs(strids)
+
+        mstridx = self.mstridx
+
+        if len_ymd > 3:
+            raise ValueError("More than three YMD values")
+        elif len_ymd == 1 or (mstridx is not None and len_ymd == 2):
+            # One member, or two members with a month string
+            if mstridx is not None:
+                month = self[mstridx]
+                # since mstridx is 0 or 1, self[mstridx-1] always
+                # looks up the other element
+                other = self[mstridx - 1]
+            else:
+                other = self[0]
+
+            if len_ymd > 1 or mstridx is None:
+                if other > 31:
+                    year = other
+                else:
+                    day = other
+
+        elif len_ymd == 2:
+            # Two members with numbers
+            if self[0] > 31:
+                # 99-01
+                year, month = self
+            elif self[1] > 31:
+                # 01-99
+                month, year = self
+            elif dayfirst and self[1] <= 12:
+                # 13-01
+                day, month = self
+            else:
+                # 01-13
+                month, day = self
+
+        elif len_ymd == 3:
+            # Three members
+            if mstridx == 0:
+                if self[1] > 31:
+                    # Apr-2003-25
+                    month, year, day = self
+                else:
+                    month, day, year = self
+            elif mstridx == 1:
+                if self[0] > 31 or (yearfirst and self[2] <= 31):
+                    # 99-Jan-01
+                    year, month, day = self
+                else:
+                    # 01-Jan-01
+                    # Give precedence to day-first, since
+                    # two-digit years is usually hand-written.
+                    day, month, year = self
+
+            elif mstridx == 2:
+                # WTF!?
+                if self[1] > 31:
+                    # 01-99-Jan
+                    day, year, month = self
+                else:
+                    # 99-01-Jan
+                    year, day, month = self
+
+            else:
+                if (self[0] > 31 or
+                    self.ystridx == 0 or
+                        (yearfirst and self[1] <= 12 and self[2] <= 31)):
+                    # 99-01-01
+                    if dayfirst and self[2] <= 12:
+                        year, day, month = self
+                    else:
+                        year, month, day = self
+                elif self[0] > 12 or (dayfirst and self[1] <= 12):
+                    # 13-01-01
+                    day, month, year = self
+                else:
+                    # 01-13-01
+                    month, day, year = self
+
+        return year, month, day
+
+
+class parser(object):
+    def __init__(self, info=None):
+        self.info = info or parserinfo()
+
+    def parse(self, timestr, default=None,
+              ignoretz=False, tzinfos=None, **kwargs):
+        """
+        Parse the date/time string into a :class:`datetime.datetime` object.
+
+        :param timestr:
+            Any date/time string using the supported formats.
+
+        :param default:
+            The default datetime object, if this is a datetime object and not
+            ``None``, elements specified in ``timestr`` replace elements in the
+            default object.
+
+        :param ignoretz:
+            If set ``True``, time zones in parsed strings are ignored and a
+            naive :class:`datetime.datetime` object is returned.
+
+        :param tzinfos:
+            Additional time zone names / aliases which may be present in the
+            string. This argument maps time zone names (and optionally offsets
+            from those time zones) to time zones. This parameter can be a
+            dictionary with timezone aliases mapping time zone names to time
+            zones or a function taking two parameters (``tzname`` and
+            ``tzoffset``) and returning a time zone.
+
+            The timezones to which the names are mapped can be an integer
+            offset from UTC in seconds or a :class:`tzinfo` object.
+
+            .. doctest::
+               :options: +NORMALIZE_WHITESPACE
+
+                >>> from dateutil.parser import parse
+                >>> from dateutil.tz import gettz
+                >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
+                >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
+                datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
+                >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
+                datetime.datetime(2012, 1, 19, 17, 21,
+                                  tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
+
+            This parameter is ignored if ``ignoretz`` is set.
+
+        :param \\*\\*kwargs:
+            Keyword arguments as passed to ``_parse()``.
+
+        :return:
+            Returns a :class:`datetime.datetime` object or, if the
+            ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
+            first element being a :class:`datetime.datetime` object, the second
+            a tuple containing the fuzzy tokens.
+
+        :raises ParserError:
+            Raised for invalid or unknown string format, if the provided
+            :class:`tzinfo` is not in a valid format, or if an invalid date
+            would be created.
+
+        :raises TypeError:
+            Raised for non-string or character stream input.
+
+        :raises OverflowError:
+            Raised if the parsed date exceeds the largest valid C integer on
+            your system.
+        """
+
+        if default is None:
+            default = datetime.datetime.now().replace(hour=0, minute=0,
+                                                      second=0, microsecond=0)
+
+        res, skipped_tokens = self._parse(timestr, **kwargs)
+
+        if res is None:
+            raise ParserError("Unknown string format: %s", timestr)
+
+        if len(res) == 0:
+            raise ParserError("String does not contain a date: %s", timestr)
+
+        try:
+            ret = self._build_naive(res, default)
+        except ValueError as e:
+            six.raise_from(ParserError(e.args[0] + ": %s", timestr), e)
+
+        if not ignoretz:
+            ret = self._build_tzaware(ret, res, tzinfos)
+
+        if kwargs.get('fuzzy_with_tokens', False):
+            return ret, skipped_tokens
+        else:
+            return ret
+
+    class _result(_resultbase):
+        __slots__ = ["year", "month", "day", "weekday",
+                     "hour", "minute", "second", "microsecond",
+                     "tzname", "tzoffset", "ampm","any_unused_tokens"]
+
+    def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
+               fuzzy_with_tokens=False):
+        """
+        Private method which performs the heavy lifting of parsing, called from
+        ``parse()``, which passes on its ``kwargs`` to this function.
+
+        :param timestr:
+            The string to parse.
+
+        :param dayfirst:
+            Whether to interpret the first value in an ambiguous 3-integer date
+            (e.g. 01/05/09) as the day (``True``) or month (``False``). If
+            ``yearfirst`` is set to ``True``, this distinguishes between YDM
+            and YMD. If set to ``None``, this value is retrieved from the
+            current :class:`parserinfo` object (which itself defaults to
+            ``False``).
+
+        :param yearfirst:
+            Whether to interpret the first value in an ambiguous 3-integer date
+            (e.g. 01/05/09) as the year. If ``True``, the first number is taken
+            to be the year, otherwise the last number is taken to be the year.
+            If this is set to ``None``, the value is retrieved from the current
+            :class:`parserinfo` object (which itself defaults to ``False``).
+
+        :param fuzzy:
+            Whether to allow fuzzy parsing, allowing for string like "Today is
+            January 1, 2047 at 8:21:00AM".
+
+        :param fuzzy_with_tokens:
+            If ``True``, ``fuzzy`` is automatically set to True, and the parser
+            will return a tuple where the first element is the parsed
+            :class:`datetime.datetime` datetimestamp and the second element is
+            a tuple containing the portions of the string which were ignored:
+
+            .. doctest::
+
+                >>> from dateutil.parser import parse
+                >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
+                (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
+
+        """
+        if fuzzy_with_tokens:
+            fuzzy = True
+
+        info = self.info
+
+        if dayfirst is None:
+            dayfirst = info.dayfirst
+
+        if yearfirst is None:
+            yearfirst = info.yearfirst
+
+        res = self._result()
+        l = _timelex.split(timestr)         # Splits the timestr into tokens
+
+        skipped_idxs = []
+
+        # year/month/day list
+        ymd = _ymd()
+
+        len_l = len(l)
+        i = 0
+        try:
+            while i < len_l:
+
+                # Check if it's a number
+                value_repr = l[i]
+                try:
+                    value = float(value_repr)
+                except ValueError:
+                    value = None
+
+                if value is not None:
+                    # Numeric token
+                    i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy)
+
+                # Check weekday
+                elif info.weekday(l[i]) is not None:
+                    value = info.weekday(l[i])
+                    res.weekday = value
+
+                # Check month name
+                elif info.month(l[i]) is not None:
+                    value = info.month(l[i])
+                    ymd.append(value, 'M')
+
+                    if i + 1 < len_l:
+                        if l[i + 1] in ('-', '/'):
+                            # Jan-01[-99]
+                            sep = l[i + 1]
+                            ymd.append(l[i + 2])
+
+                            if i + 3 < len_l and l[i + 3] == sep:
+                                # Jan-01-99
+                                ymd.append(l[i + 4])
+                                i += 2
+
+                            i += 2
+
+                        elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and
+                              info.pertain(l[i + 2])):
+                            # Jan of 01
+                            # In this case, 01 is clearly year
+                            if l[i + 4].isdigit():
+                                # Convert it here to become unambiguous
+                                value = int(l[i + 4])
+                                year = str(info.convertyear(value))
+                                ymd.append(year, 'Y')
+                            else:
+                                # Wrong guess
+                                pass
+                                # TODO: not hit in tests
+                            i += 4
+
+                # Check am/pm
+                elif info.ampm(l[i]) is not None:
+                    value = info.ampm(l[i])
+                    val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy)
+
+                    if val_is_ampm:
+                        res.hour = self._adjust_ampm(res.hour, value)
+                        res.ampm = value
+
+                    elif fuzzy:
+                        skipped_idxs.append(i)
+
+                # Check for a timezone name
+                elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]):
+                    res.tzname = l[i]
+                    res.tzoffset = info.tzoffset(res.tzname)
+
+                    # Check for something like GMT+3, or BRST+3. Notice
+                    # that it doesn't mean "I am 3 hours after GMT", but
+                    # "my time +3 is GMT". If found, we reverse the
+                    # logic so that timezone parsing code will get it
+                    # right.
+                    if i + 1 < len_l and l[i + 1] in ('+', '-'):
+                        l[i + 1] = ('+', '-')[l[i + 1] == '+']
+                        res.tzoffset = None
+                        if info.utczone(res.tzname):
+                            # With something like GMT+3, the timezone
+                            # is *not* GMT.
+                            res.tzname = None
+
+                # Check for a numbered timezone
+                elif res.hour is not None and l[i] in ('+', '-'):
+                    signal = (-1, 1)[l[i] == '+']
+                    len_li = len(l[i + 1])
+
+                    # TODO: check that l[i + 1] is integer?
+                    if len_li == 4:
+                        # -0300
+                        hour_offset = int(l[i + 1][:2])
+                        min_offset = int(l[i + 1][2:])
+                    elif i + 2 < len_l and l[i + 2] == ':':
+                        # -03:00
+                        hour_offset = int(l[i + 1])
+                        min_offset = int(l[i + 3])  # TODO: Check that l[i+3] is minute-like?
+                        i += 2
+                    elif len_li <= 2:
+                        # -[0]3
+                        hour_offset = int(l[i + 1][:2])
+                        min_offset = 0
+                    else:
+                        raise ValueError(timestr)
+
+                    res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60)
+
+                    # Look for a timezone name between parenthesis
+                    if (i + 5 < len_l and
+                            info.jump(l[i + 2]) and l[i + 3] == '(' and
+                            l[i + 5] == ')' and
+                            3 <= len(l[i + 4]) and
+                            self._could_be_tzname(res.hour, res.tzname,
+                                                  None, l[i + 4])):
+                        # -0300 (BRST)
+                        res.tzname = l[i + 4]
+                        i += 4
+
+                    i += 1
+
+                # Check jumps
+                elif not (info.jump(l[i]) or fuzzy):
+                    raise ValueError(timestr)
+
+                else:
+                    skipped_idxs.append(i)
+                i += 1
+
+            # Process year/month/day
+            year, month, day = ymd.resolve_ymd(yearfirst, dayfirst)
+
+            res.century_specified = ymd.century_specified
+            res.year = year
+            res.month = month
+            res.day = day
+
+        except (IndexError, ValueError):
+            return None, None
+
+        if not info.validate(res):
+            return None, None
+
+        if fuzzy_with_tokens:
+            skipped_tokens = self._recombine_skipped(l, skipped_idxs)
+            return res, tuple(skipped_tokens)
+        else:
+            return res, None
+
+    def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy):
+        # Token is a number
+        value_repr = tokens[idx]
+        try:
+            value = self._to_decimal(value_repr)
+        except Exception as e:
+            six.raise_from(ValueError('Unknown numeric token'), e)
+
+        len_li = len(value_repr)
+
+        len_l = len(tokens)
+
+        if (len(ymd) == 3 and len_li in (2, 4) and
+            res.hour is None and
+            (idx + 1 >= len_l or
+             (tokens[idx + 1] != ':' and
+              info.hms(tokens[idx + 1]) is None))):
+            # 19990101T23[59]
+            s = tokens[idx]
+            res.hour = int(s[:2])
+
+            if len_li == 4:
+                res.minute = int(s[2:])
+
+        elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6):
+            # YYMMDD or HHMMSS[.ss]
+            s = tokens[idx]
+
+            if not ymd and '.' not in tokens[idx]:
+                ymd.append(s[:2])
+                ymd.append(s[2:4])
+                ymd.append(s[4:])
+            else:
+                # 19990101T235959[.59]
+
+                # TODO: Check if res attributes already set.
+                res.hour = int(s[:2])
+                res.minute = int(s[2:4])
+                res.second, res.microsecond = self._parsems(s[4:])
+
+        elif len_li in (8, 12, 14):
+            # YYYYMMDD
+            s = tokens[idx]
+            ymd.append(s[:4], 'Y')
+            ymd.append(s[4:6])
+            ymd.append(s[6:8])
+
+            if len_li > 8:
+                res.hour = int(s[8:10])
+                res.minute = int(s[10:12])
+
+                if len_li > 12:
+                    res.second = int(s[12:])
+
+        elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None:
+            # HH[ ]h or MM[ ]m or SS[.ss][ ]s
+            hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True)
+            (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx)
+            if hms is not None:
+                # TODO: checking that hour/minute/second are not
+                # already set?
+                self._assign_hms(res, value_repr, hms)
+
+        elif idx + 2 < len_l and tokens[idx + 1] == ':':
+            # HH:MM[:SS[.ss]]
+            res.hour = int(value)
+            value = self._to_decimal(tokens[idx + 2])  # TODO: try/except for this?
+            (res.minute, res.second) = self._parse_min_sec(value)
+
+            if idx + 4 < len_l and tokens[idx + 3] == ':':
+                res.second, res.microsecond = self._parsems(tokens[idx + 4])
+
+                idx += 2
+
+            idx += 2
+
+        elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'):
+            sep = tokens[idx + 1]
+            ymd.append(value_repr)
+
+            if idx + 2 < len_l and not info.jump(tokens[idx + 2]):
+                if tokens[idx + 2].isdigit():
+                    # 01-01[-01]
+                    ymd.append(tokens[idx + 2])
+                else:
+                    # 01-Jan[-01]
+                    value = info.month(tokens[idx + 2])
+
+                    if value is not None:
+                        ymd.append(value, 'M')
+                    else:
+                        raise ValueError()
+
+                if idx + 3 < len_l and tokens[idx + 3] == sep:
+                    # We have three members
+                    value = info.month(tokens[idx + 4])
+
+                    if value is not None:
+                        ymd.append(value, 'M')
+                    else:
+                        ymd.append(tokens[idx + 4])
+                    idx += 2
+
+                idx += 1
+            idx += 1
+
+        elif idx + 1 >= len_l or info.jump(tokens[idx + 1]):
+            if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None:
+                # 12 am
+                hour = int(value)
+                res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2]))
+                idx += 1
+            else:
+                # Year, month or day
+                ymd.append(value)
+            idx += 1
+
+        elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24):
+            # 12am
+            hour = int(value)
+            res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1]))
+            idx += 1
+
+        elif ymd.could_be_day(value):
+            ymd.append(value)
+
+        elif not fuzzy:
+            raise ValueError()
+
+        return idx
+
+    def _find_hms_idx(self, idx, tokens, info, allow_jump):
+        len_l = len(tokens)
+
+        if idx+1 < len_l and info.hms(tokens[idx+1]) is not None:
+            # There is an "h", "m", or "s" label following this token.  We take
+            # assign the upcoming label to the current token.
+            # e.g. the "12" in 12h"
+            hms_idx = idx + 1
+
+        elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and
+              info.hms(tokens[idx+2]) is not None):
+            # There is a space and then an "h", "m", or "s" label.
+            # e.g. the "12" in "12 h"
+            hms_idx = idx + 2
+
+        elif idx > 0 and info.hms(tokens[idx-1]) is not None:
+            # There is a "h", "m", or "s" preceding this token.  Since neither
+            # of the previous cases was hit, there is no label following this
+            # token, so we use the previous label.
+            # e.g. the "04" in "12h04"
+            hms_idx = idx-1
+
+        elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and
+              info.hms(tokens[idx-2]) is not None):
+            # If we are looking at the final token, we allow for a
+            # backward-looking check to skip over a space.
+            # TODO: Are we sure this is the right condition here?
+            hms_idx = idx - 2
+
+        else:
+            hms_idx = None
+
+        return hms_idx
+
+    def _assign_hms(self, res, value_repr, hms):
+        # See GH issue #427, fixing float rounding
+        value = self._to_decimal(value_repr)
+
+        if hms == 0:
+            # Hour
+            res.hour = int(value)
+            if value % 1:
+                res.minute = int(60*(value % 1))
+
+        elif hms == 1:
+            (res.minute, res.second) = self._parse_min_sec(value)
+
+        elif hms == 2:
+            (res.second, res.microsecond) = self._parsems(value_repr)
+
+    def _could_be_tzname(self, hour, tzname, tzoffset, token):
+        return (hour is not None and
+                tzname is None and
+                tzoffset is None and
+                len(token) <= 5 and
+                (all(x in string.ascii_uppercase for x in token)
+                 or token in self.info.UTCZONE))
+
+    def _ampm_valid(self, hour, ampm, fuzzy):
+        """
+        For fuzzy parsing, 'a' or 'am' (both valid English words)
+        may erroneously trigger the AM/PM flag. Deal with that
+        here.
+        """
+        val_is_ampm = True
+
+        # If there's already an AM/PM flag, this one isn't one.
+        if fuzzy and ampm is not None:
+            val_is_ampm = False
+
+        # If AM/PM is found and hour is not, raise a ValueError
+        if hour is None:
+            if fuzzy:
+                val_is_ampm = False
+            else:
+                raise ValueError('No hour specified with AM or PM flag.')
+        elif not 0 <= hour <= 12:
+            # If AM/PM is found, it's a 12 hour clock, so raise
+            # an error for invalid range
+            if fuzzy:
+                val_is_ampm = False
+            else:
+                raise ValueError('Invalid hour specified for 12-hour clock.')
+
+        return val_is_ampm
+
+    def _adjust_ampm(self, hour, ampm):
+        if hour < 12 and ampm == 1:
+            hour += 12
+        elif hour == 12 and ampm == 0:
+            hour = 0
+        return hour
+
+    def _parse_min_sec(self, value):
+        # TODO: Every usage of this function sets res.second to the return
+        # value. Are there any cases where second will be returned as None and
+        # we *don't* want to set res.second = None?
+        minute = int(value)
+        second = None
+
+        sec_remainder = value % 1
+        if sec_remainder:
+            second = int(60 * sec_remainder)
+        return (minute, second)
+
+    def _parse_hms(self, idx, tokens, info, hms_idx):
+        # TODO: Is this going to admit a lot of false-positives for when we
+        # just happen to have digits and "h", "m" or "s" characters in non-date
+        # text?  I guess hex hashes won't have that problem, but there's plenty
+        # of random junk out there.
+        if hms_idx is None:
+            hms = None
+            new_idx = idx
+        elif hms_idx > idx:
+            hms = info.hms(tokens[hms_idx])
+            new_idx = hms_idx
+        else:
+            # Looking backwards, increment one.
+            hms = info.hms(tokens[hms_idx]) + 1
+            new_idx = idx
+
+        return (new_idx, hms)
+
+    # ------------------------------------------------------------------
+    # Handling for individual tokens.  These are kept as methods instead
+    #  of functions for the sake of customizability via subclassing.
+
+    def _parsems(self, value):
+        """Parse a I[.F] seconds value into (seconds, microseconds)."""
+        if "." not in value:
+            return int(value), 0
+        else:
+            i, f = value.split(".")
+            return int(i), int(f.ljust(6, "0")[:6])
+
+    def _to_decimal(self, val):
+        try:
+            decimal_value = Decimal(val)
+            # See GH 662, edge case, infinite value should not be converted
+            #  via `_to_decimal`
+            if not decimal_value.is_finite():
+                raise ValueError("Converted decimal value is infinite or NaN")
+        except Exception as e:
+            msg = "Could not convert %s to decimal" % val
+            six.raise_from(ValueError(msg), e)
+        else:
+            return decimal_value
+
+    # ------------------------------------------------------------------
+    # Post-Parsing construction of datetime output.  These are kept as
+    #  methods instead of functions for the sake of customizability via
+    #  subclassing.
+
+    def _build_tzinfo(self, tzinfos, tzname, tzoffset):
+        if callable(tzinfos):
+            tzdata = tzinfos(tzname, tzoffset)
+        else:
+            tzdata = tzinfos.get(tzname)
+        # handle case where tzinfo is paased an options that returns None
+        # eg tzinfos = {'BRST' : None}
+        if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
+            tzinfo = tzdata
+        elif isinstance(tzdata, text_type):
+            tzinfo = tz.tzstr(tzdata)
+        elif isinstance(tzdata, integer_types):
+            tzinfo = tz.tzoffset(tzname, tzdata)
+        else:
+            raise TypeError("Offset must be tzinfo subclass, tz string, "
+                            "or int offset.")
+        return tzinfo
+
+    def _build_tzaware(self, naive, res, tzinfos):
+        if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)):
+            tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset)
+            aware = naive.replace(tzinfo=tzinfo)
+            aware = self._assign_tzname(aware, res.tzname)
+
+        elif res.tzname and res.tzname in time.tzname:
+            aware = naive.replace(tzinfo=tz.tzlocal())
+
+            # Handle ambiguous local datetime
+            aware = self._assign_tzname(aware, res.tzname)
+
+            # This is mostly relevant for winter GMT zones parsed in the UK
+            if (aware.tzname() != res.tzname and
+                    res.tzname in self.info.UTCZONE):
+                aware = aware.replace(tzinfo=tz.UTC)
+
+        elif res.tzoffset == 0:
+            aware = naive.replace(tzinfo=tz.UTC)
+
+        elif res.tzoffset:
+            aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
+
+        elif not res.tzname and not res.tzoffset:
+            # i.e. no timezone information was found.
+            aware = naive
+
+        elif res.tzname:
+            # tz-like string was parsed but we don't know what to do
+            # with it
+            warnings.warn("tzname {tzname} identified but not understood.  "
+                          "Pass `tzinfos` argument in order to correctly "
+                          "return a timezone-aware datetime.  In a future "
+                          "version, this will raise an "
+                          "exception.".format(tzname=res.tzname),
+                          category=UnknownTimezoneWarning)
+            aware = naive
+
+        return aware
+
+    def _build_naive(self, res, default):
+        repl = {}
+        for attr in ("year", "month", "day", "hour",
+                     "minute", "second", "microsecond"):
+            value = getattr(res, attr)
+            if value is not None:
+                repl[attr] = value
+
+        if 'day' not in repl:
+            # If the default day exceeds the last day of the month, fall back
+            # to the end of the month.
+            cyear = default.year if res.year is None else res.year
+            cmonth = default.month if res.month is None else res.month
+            cday = default.day if res.day is None else res.day
+
+            if cday > monthrange(cyear, cmonth)[1]:
+                repl['day'] = monthrange(cyear, cmonth)[1]
+
+        naive = default.replace(**repl)
+
+        if res.weekday is not None and not res.day:
+            naive = naive + relativedelta.relativedelta(weekday=res.weekday)
+
+        return naive
+
+    def _assign_tzname(self, dt, tzname):
+        if dt.tzname() != tzname:
+            new_dt = tz.enfold(dt, fold=1)
+            if new_dt.tzname() == tzname:
+                return new_dt
+
+        return dt
+
+    def _recombine_skipped(self, tokens, skipped_idxs):
+        """
+        >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"]
+        >>> skipped_idxs = [0, 1, 2, 5]
+        >>> _recombine_skipped(tokens, skipped_idxs)
+        ["foo bar", "baz"]
+        """
+        skipped_tokens = []
+        for i, idx in enumerate(sorted(skipped_idxs)):
+            if i > 0 and idx - 1 == skipped_idxs[i - 1]:
+                skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx]
+            else:
+                skipped_tokens.append(tokens[idx])
+
+        return skipped_tokens
+
+
+DEFAULTPARSER = parser()
+
+
+def parse(timestr, parserinfo=None, **kwargs):
+    """
+
+    Parse a string in one of the supported formats, using the
+    ``parserinfo`` parameters.
+
+    :param timestr:
+        A string containing a date/time stamp.
+
+    :param parserinfo:
+        A :class:`parserinfo` object containing parameters for the parser.
+        If ``None``, the default arguments to the :class:`parserinfo`
+        constructor are used.
+
+    The ``**kwargs`` parameter takes the following keyword arguments:
+
+    :param default:
+        The default datetime object, if this is a datetime object and not
+        ``None``, elements specified in ``timestr`` replace elements in the
+        default object.
+
+    :param ignoretz:
+        If set ``True``, time zones in parsed strings are ignored and a naive
+        :class:`datetime` object is returned.
+
+    :param tzinfos:
+        Additional time zone names / aliases which may be present in the
+        string. This argument maps time zone names (and optionally offsets
+        from those time zones) to time zones. This parameter can be a
+        dictionary with timezone aliases mapping time zone names to time
+        zones or a function taking two parameters (``tzname`` and
+        ``tzoffset``) and returning a time zone.
+
+        The timezones to which the names are mapped can be an integer
+        offset from UTC in seconds or a :class:`tzinfo` object.
+
+        .. doctest::
+           :options: +NORMALIZE_WHITESPACE
+
+            >>> from dateutil.parser import parse
+            >>> from dateutil.tz import gettz
+            >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
+            >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
+            datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
+            >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
+            datetime.datetime(2012, 1, 19, 17, 21,
+                              tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
+
+        This parameter is ignored if ``ignoretz`` is set.
+
+    :param dayfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the day (``True``) or month (``False``). If
+        ``yearfirst`` is set to ``True``, this distinguishes between YDM and
+        YMD. If set to ``None``, this value is retrieved from the current
+        :class:`parserinfo` object (which itself defaults to ``False``).
+
+    :param yearfirst:
+        Whether to interpret the first value in an ambiguous 3-integer date
+        (e.g. 01/05/09) as the year. If ``True``, the first number is taken to
+        be the year, otherwise the last number is taken to be the year. If
+        this is set to ``None``, the value is retrieved from the current
+        :class:`parserinfo` object (which itself defaults to ``False``).
+
+    :param fuzzy:
+        Whether to allow fuzzy parsing, allowing for string like "Today is
+        January 1, 2047 at 8:21:00AM".
+
+    :param fuzzy_with_tokens:
+        If ``True``, ``fuzzy`` is automatically set to True, and the parser
+        will return a tuple where the first element is the parsed
+        :class:`datetime.datetime` datetimestamp and the second element is
+        a tuple containing the portions of the string which were ignored:
+
+        .. doctest::
+
+            >>> from dateutil.parser import parse
+            >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
+            (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
+
+    :return:
+        Returns a :class:`datetime.datetime` object or, if the
+        ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
+        first element being a :class:`datetime.datetime` object, the second
+        a tuple containing the fuzzy tokens.
+
+    :raises ValueError:
+        Raised for invalid or unknown string format, if the provided
+        :class:`tzinfo` is not in a valid format, or if an invalid date
+        would be created.
+
+    :raises OverflowError:
+        Raised if the parsed date exceeds the largest valid C integer on
+        your system.
+    """
+    if parserinfo:
+        return parser(parserinfo).parse(timestr, **kwargs)
+    else:
+        return DEFAULTPARSER.parse(timestr, **kwargs)
+
+
+class _tzparser(object):
+
+    class _result(_resultbase):
+
+        __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
+                     "start", "end"]
+
+        class _attr(_resultbase):
+            __slots__ = ["month", "week", "weekday",
+                         "yday", "jyday", "day", "time"]
+
+        def __repr__(self):
+            return self._repr("")
+
+        def __init__(self):
+            _resultbase.__init__(self)
+            self.start = self._attr()
+            self.end = self._attr()
+
+    def parse(self, tzstr):
+        res = self._result()
+        l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x]
+        used_idxs = list()
+        try:
+
+            len_l = len(l)
+
+            i = 0
+            while i < len_l:
+                # BRST+3[BRDT[+2]]
+                j = i
+                while j < len_l and not [x for x in l[j]
+                                         if x in "0123456789:,-+"]:
+                    j += 1
+                if j != i:
+                    if not res.stdabbr:
+                        offattr = "stdoffset"
+                        res.stdabbr = "".join(l[i:j])
+                    else:
+                        offattr = "dstoffset"
+                        res.dstabbr = "".join(l[i:j])
+
+                    for ii in range(j):
+                        used_idxs.append(ii)
+                    i = j
+                    if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
+                                       "0123456789")):
+                        if l[i] in ('+', '-'):
+                            # Yes, that's right.  See the TZ variable
+                            # documentation.
+                            signal = (1, -1)[l[i] == '+']
+                            used_idxs.append(i)
+                            i += 1
+                        else:
+                            signal = -1
+                        len_li = len(l[i])
+                        if len_li == 4:
+                            # -0300
+                            setattr(res, offattr, (int(l[i][:2]) * 3600 +
+                                                   int(l[i][2:]) * 60) * signal)
+                        elif i + 1 < len_l and l[i + 1] == ':':
+                            # -03:00
+                            setattr(res, offattr,
+                                    (int(l[i]) * 3600 +
+                                     int(l[i + 2]) * 60) * signal)
+                            used_idxs.append(i)
+                            i += 2
+                        elif len_li <= 2:
+                            # -[0]3
+                            setattr(res, offattr,
+                                    int(l[i][:2]) * 3600 * signal)
+                        else:
+                            return None
+                        used_idxs.append(i)
+                        i += 1
+                    if res.dstabbr:
+                        break
+                else:
+                    break
+
+
+            if i < len_l:
+                for j in range(i, len_l):
+                    if l[j] == ';':
+                        l[j] = ','
+
+                assert l[i] == ','
+
+                i += 1
+
+            if i >= len_l:
+                pass
+            elif (8 <= l.count(',') <= 9 and
+                  not [y for x in l[i:] if x != ','
+                       for y in x if y not in "0123456789+-"]):
+                # GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
+                for x in (res.start, res.end):
+                    x.month = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                    if l[i] == '-':
+                        value = int(l[i + 1]) * -1
+                        used_idxs.append(i)
+                        i += 1
+                    else:
+                        value = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                    if value:
+                        x.week = value
+                        x.weekday = (int(l[i]) - 1) % 7
+                    else:
+                        x.day = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                    x.time = int(l[i])
+                    used_idxs.append(i)
+                    i += 2
+                if i < len_l:
+                    if l[i] in ('-', '+'):
+                        signal = (-1, 1)[l[i] == "+"]
+                        used_idxs.append(i)
+                        i += 1
+                    else:
+                        signal = 1
+                    used_idxs.append(i)
+                    res.dstoffset = (res.stdoffset + int(l[i]) * signal)
+
+                # This was a made-up format that is not in normal use
+                warn(('Parsed time zone "%s"' % tzstr) +
+                     'is in a non-standard dateutil-specific format, which ' +
+                     'is now deprecated; support for parsing this format ' +
+                     'will be removed in future versions. It is recommended ' +
+                     'that you switch to a standard format like the GNU ' +
+                     'TZ variable format.', tz.DeprecatedTzFormatWarning)
+            elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
+                  not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
+                                                     '.', '-', ':')
+                       for y in x if y not in "0123456789"]):
+                for x in (res.start, res.end):
+                    if l[i] == 'J':
+                        # non-leap year day (1 based)
+                        used_idxs.append(i)
+                        i += 1
+                        x.jyday = int(l[i])
+                    elif l[i] == 'M':
+                        # month[-.]week[-.]weekday
+                        used_idxs.append(i)
+                        i += 1
+                        x.month = int(l[i])
+                        used_idxs.append(i)
+                        i += 1
+                        assert l[i] in ('-', '.')
+                        used_idxs.append(i)
+                        i += 1
+                        x.week = int(l[i])
+                        if x.week == 5:
+                            x.week = -1
+                        used_idxs.append(i)
+                        i += 1
+                        assert l[i] in ('-', '.')
+                        used_idxs.append(i)
+                        i += 1
+                        x.weekday = (int(l[i]) - 1) % 7
+                    else:
+                        # year day (zero based)
+                        x.yday = int(l[i]) + 1
+
+                    used_idxs.append(i)
+                    i += 1
+
+                    if i < len_l and l[i] == '/':
+                        used_idxs.append(i)
+                        i += 1
+                        # start time
+                        len_li = len(l[i])
+                        if len_li == 4:
+                            # -0300
+                            x.time = (int(l[i][:2]) * 3600 +
+                                      int(l[i][2:]) * 60)
+                        elif i + 1 < len_l and l[i + 1] == ':':
+                            # -03:00
+                            x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60
+                            used_idxs.append(i)
+                            i += 2
+                            if i + 1 < len_l and l[i + 1] == ':':
+                                used_idxs.append(i)
+                                i += 2
+                                x.time += int(l[i])
+                        elif len_li <= 2:
+                            # -[0]3
+                            x.time = (int(l[i][:2]) * 3600)
+                        else:
+                            return None
+                        used_idxs.append(i)
+                        i += 1
+
+                    assert i == len_l or l[i] == ','
+
+                    i += 1
+
+                assert i >= len_l
+
+        except (IndexError, ValueError, AssertionError):
+            return None
+
+        unused_idxs = set(range(len_l)).difference(used_idxs)
+        res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"})
+        return res
+
+
+DEFAULTTZPARSER = _tzparser()
+
+
+def _parsetz(tzstr):
+    return DEFAULTTZPARSER.parse(tzstr)
+
+
+class ParserError(ValueError):
+    """Error class for representing failure to parse a datetime string."""
+    def __str__(self):
+        try:
+            return self.args[0] % self.args[1:]
+        except (TypeError, IndexError):
+            return super(ParserError, self).__str__()
+
+        def __repr__(self):
+            return "%s(%s)" % (self.__class__.__name__, str(self))
+
+
+class UnknownTimezoneWarning(RuntimeWarning):
+    """Raised when the parser finds a timezone it cannot parse into a tzinfo"""
+# vim:ts=4:sw=4:et

+ 411 - 0
venv/lib/python3.8/site-packages/dateutil/parser/isoparser.py

@@ -0,0 +1,411 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers a parser for ISO-8601 strings
+
+It is intended to support all valid date, time and datetime formats per the
+ISO-8601 specification.
+
+..versionadded:: 2.7.0
+"""
+from datetime import datetime, timedelta, time, date
+import calendar
+from dateutil import tz
+
+from functools import wraps
+
+import re
+import six
+
+__all__ = ["isoparse", "isoparser"]
+
+
+def _takes_ascii(f):
+    @wraps(f)
+    def func(self, str_in, *args, **kwargs):
+        # If it's a stream, read the whole thing
+        str_in = getattr(str_in, 'read', lambda: str_in)()
+
+        # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
+        if isinstance(str_in, six.text_type):
+            # ASCII is the same in UTF-8
+            try:
+                str_in = str_in.encode('ascii')
+            except UnicodeEncodeError as e:
+                msg = 'ISO-8601 strings should contain only ASCII characters'
+                six.raise_from(ValueError(msg), e)
+
+        return f(self, str_in, *args, **kwargs)
+
+    return func
+
+
+class isoparser(object):
+    def __init__(self, sep=None):
+        """
+        :param sep:
+            A single character that separates date and time portions. If
+            ``None``, the parser will accept any single character.
+            For strict ISO-8601 adherence, pass ``'T'``.
+        """
+        if sep is not None:
+            if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
+                raise ValueError('Separator must be a single, non-numeric ' +
+                                 'ASCII character')
+
+            sep = sep.encode('ascii')
+
+        self._sep = sep
+
+    @_takes_ascii
+    def isoparse(self, dt_str):
+        """
+        Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
+
+        An ISO-8601 datetime string consists of a date portion, followed
+        optionally by a time portion - the date and time portions are separated
+        by a single character separator, which is ``T`` in the official
+        standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
+        combined with a time portion.
+
+        Supported date formats are:
+
+        Common:
+
+        - ``YYYY``
+        - ``YYYY-MM`` or ``YYYYMM``
+        - ``YYYY-MM-DD`` or ``YYYYMMDD``
+
+        Uncommon:
+
+        - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
+        - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
+
+        The ISO week and day numbering follows the same logic as
+        :func:`datetime.date.isocalendar`.
+
+        Supported time formats are:
+
+        - ``hh``
+        - ``hh:mm`` or ``hhmm``
+        - ``hh:mm:ss`` or ``hhmmss``
+        - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
+
+        Midnight is a special case for `hh`, as the standard supports both
+        00:00 and 24:00 as a representation. The decimal separator can be
+        either a dot or a comma.
+
+
+        .. caution::
+
+            Support for fractional components other than seconds is part of the
+            ISO-8601 standard, but is not currently implemented in this parser.
+
+        Supported time zone offset formats are:
+
+        - `Z` (UTC)
+        - `±HH:MM`
+        - `±HHMM`
+        - `±HH`
+
+        Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
+        with the exception of UTC, which will be represented as
+        :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
+        as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
+
+        :param dt_str:
+            A string or stream containing only an ISO-8601 datetime string
+
+        :return:
+            Returns a :class:`datetime.datetime` representing the string.
+            Unspecified components default to their lowest value.
+
+        .. warning::
+
+            As of version 2.7.0, the strictness of the parser should not be
+            considered a stable part of the contract. Any valid ISO-8601 string
+            that parses correctly with the default settings will continue to
+            parse correctly in future versions, but invalid strings that
+            currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
+            guaranteed to continue failing in future versions if they encode
+            a valid date.
+
+        .. versionadded:: 2.7.0
+        """
+        components, pos = self._parse_isodate(dt_str)
+
+        if len(dt_str) > pos:
+            if self._sep is None or dt_str[pos:pos + 1] == self._sep:
+                components += self._parse_isotime(dt_str[pos + 1:])
+            else:
+                raise ValueError('String contains unknown ISO components')
+
+        if len(components) > 3 and components[3] == 24:
+            components[3] = 0
+            return datetime(*components) + timedelta(days=1)
+
+        return datetime(*components)
+
+    @_takes_ascii
+    def parse_isodate(self, datestr):
+        """
+        Parse the date portion of an ISO string.
+
+        :param datestr:
+            The string portion of an ISO string, without a separator
+
+        :return:
+            Returns a :class:`datetime.date` object
+        """
+        components, pos = self._parse_isodate(datestr)
+        if pos < len(datestr):
+            raise ValueError('String contains unknown ISO ' +
+                             'components: {}'.format(datestr))
+        return date(*components)
+
+    @_takes_ascii
+    def parse_isotime(self, timestr):
+        """
+        Parse the time portion of an ISO string.
+
+        :param timestr:
+            The time portion of an ISO string, without a separator
+
+        :return:
+            Returns a :class:`datetime.time` object
+        """
+        components = self._parse_isotime(timestr)
+        if components[0] == 24:
+            components[0] = 0
+        return time(*components)
+
+    @_takes_ascii
+    def parse_tzstr(self, tzstr, zero_as_utc=True):
+        """
+        Parse a valid ISO time zone string.
+
+        See :func:`isoparser.isoparse` for details on supported formats.
+
+        :param tzstr:
+            A string representing an ISO time zone offset
+
+        :param zero_as_utc:
+            Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
+
+        :return:
+            Returns :class:`dateutil.tz.tzoffset` for offsets and
+            :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
+            specified) offsets equivalent to UTC.
+        """
+        return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
+
+    # Constants
+    _DATE_SEP = b'-'
+    _TIME_SEP = b':'
+    _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')
+
+    def _parse_isodate(self, dt_str):
+        try:
+            return self._parse_isodate_common(dt_str)
+        except ValueError:
+            return self._parse_isodate_uncommon(dt_str)
+
+    def _parse_isodate_common(self, dt_str):
+        len_str = len(dt_str)
+        components = [1, 1, 1]
+
+        if len_str < 4:
+            raise ValueError('ISO string too short')
+
+        # Year
+        components[0] = int(dt_str[0:4])
+        pos = 4
+        if pos >= len_str:
+            return components, pos
+
+        has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
+        if has_sep:
+            pos += 1
+
+        # Month
+        if len_str - pos < 2:
+            raise ValueError('Invalid common month')
+
+        components[1] = int(dt_str[pos:pos + 2])
+        pos += 2
+
+        if pos >= len_str:
+            if has_sep:
+                return components, pos
+            else:
+                raise ValueError('Invalid ISO format')
+
+        if has_sep:
+            if dt_str[pos:pos + 1] != self._DATE_SEP:
+                raise ValueError('Invalid separator in ISO string')
+            pos += 1
+
+        # Day
+        if len_str - pos < 2:
+            raise ValueError('Invalid common day')
+        components[2] = int(dt_str[pos:pos + 2])
+        return components, pos + 2
+
+    def _parse_isodate_uncommon(self, dt_str):
+        if len(dt_str) < 4:
+            raise ValueError('ISO string too short')
+
+        # All ISO formats start with the year
+        year = int(dt_str[0:4])
+
+        has_sep = dt_str[4:5] == self._DATE_SEP
+
+        pos = 4 + has_sep       # Skip '-' if it's there
+        if dt_str[pos:pos + 1] == b'W':
+            # YYYY-?Www-?D?
+            pos += 1
+            weekno = int(dt_str[pos:pos + 2])
+            pos += 2
+
+            dayno = 1
+            if len(dt_str) > pos:
+                if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
+                    raise ValueError('Inconsistent use of dash separator')
+
+                pos += has_sep
+
+                dayno = int(dt_str[pos:pos + 1])
+                pos += 1
+
+            base_date = self._calculate_weekdate(year, weekno, dayno)
+        else:
+            # YYYYDDD or YYYY-DDD
+            if len(dt_str) - pos < 3:
+                raise ValueError('Invalid ordinal day')
+
+            ordinal_day = int(dt_str[pos:pos + 3])
+            pos += 3
+
+            if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
+                raise ValueError('Invalid ordinal day' +
+                                 ' {} for year {}'.format(ordinal_day, year))
+
+            base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
+
+        components = [base_date.year, base_date.month, base_date.day]
+        return components, pos
+
+    def _calculate_weekdate(self, year, week, day):
+        """
+        Calculate the day of corresponding to the ISO year-week-day calendar.
+
+        This function is effectively the inverse of
+        :func:`datetime.date.isocalendar`.
+
+        :param year:
+            The year in the ISO calendar
+
+        :param week:
+            The week in the ISO calendar - range is [1, 53]
+
+        :param day:
+            The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
+
+        :return:
+            Returns a :class:`datetime.date`
+        """
+        if not 0 < week < 54:
+            raise ValueError('Invalid week: {}'.format(week))
+
+        if not 0 < day < 8:     # Range is 1-7
+            raise ValueError('Invalid weekday: {}'.format(day))
+
+        # Get week 1 for the specific year:
+        jan_4 = date(year, 1, 4)   # Week 1 always has January 4th in it
+        week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
+
+        # Now add the specific number of weeks and days to get what we want
+        week_offset = (week - 1) * 7 + (day - 1)
+        return week_1 + timedelta(days=week_offset)
+
+    def _parse_isotime(self, timestr):
+        len_str = len(timestr)
+        components = [0, 0, 0, 0, None]
+        pos = 0
+        comp = -1
+
+        if len(timestr) < 2:
+            raise ValueError('ISO time too short')
+
+        has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
+
+        while pos < len_str and comp < 5:
+            comp += 1
+
+            if timestr[pos:pos + 1] in b'-+Zz':
+                # Detect time zone boundary
+                components[-1] = self._parse_tzstr(timestr[pos:])
+                pos = len_str
+                break
+
+            if comp < 3:
+                # Hour, minute, second
+                components[comp] = int(timestr[pos:pos + 2])
+                pos += 2
+                if (has_sep and pos < len_str and
+                        timestr[pos:pos + 1] == self._TIME_SEP):
+                    pos += 1
+
+            if comp == 3:
+                # Fraction of a second
+                frac = self._FRACTION_REGEX.match(timestr[pos:])
+                if not frac:
+                    continue
+
+                us_str = frac.group(1)[:6]  # Truncate to microseconds
+                components[comp] = int(us_str) * 10**(6 - len(us_str))
+                pos += len(frac.group())
+
+        if pos < len_str:
+            raise ValueError('Unused components in ISO string')
+
+        if components[0] == 24:
+            # Standard supports 00:00 and 24:00 as representations of midnight
+            if any(component != 0 for component in components[1:4]):
+                raise ValueError('Hour may only be 24 at 24:00:00.000')
+
+        return components
+
+    def _parse_tzstr(self, tzstr, zero_as_utc=True):
+        if tzstr == b'Z' or tzstr == b'z':
+            return tz.UTC
+
+        if len(tzstr) not in {3, 5, 6}:
+            raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
+
+        if tzstr[0:1] == b'-':
+            mult = -1
+        elif tzstr[0:1] == b'+':
+            mult = 1
+        else:
+            raise ValueError('Time zone offset requires sign')
+
+        hours = int(tzstr[1:3])
+        if len(tzstr) == 3:
+            minutes = 0
+        else:
+            minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
+
+        if zero_as_utc and hours == 0 and minutes == 0:
+            return tz.UTC
+        else:
+            if minutes > 59:
+                raise ValueError('Invalid minutes in time zone offset')
+
+            if hours > 23:
+                raise ValueError('Invalid hours in time zone offset')
+
+            return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
+
+
+DEFAULT_ISOPARSER = isoparser()
+isoparse = DEFAULT_ISOPARSER.isoparse

+ 599 - 0
venv/lib/python3.8/site-packages/dateutil/relativedelta.py

@@ -0,0 +1,599 @@
+# -*- coding: utf-8 -*-
+import datetime
+import calendar
+
+import operator
+from math import copysign
+
+from six import integer_types
+from warnings import warn
+
+from ._common import weekday
+
+MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
+
+__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
+
+
+class relativedelta(object):
+    """
+    The relativedelta type is designed to be applied to an existing datetime and
+    can replace specific components of that datetime, or represents an interval
+    of time.
+
+    It is based on the specification of the excellent work done by M.-A. Lemburg
+    in his
+    `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
+    However, notice that this type does *NOT* implement the same algorithm as
+    his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
+
+    There are two different ways to build a relativedelta instance. The
+    first one is passing it two date/datetime classes::
+
+        relativedelta(datetime1, datetime2)
+
+    The second one is passing it any number of the following keyword arguments::
+
+        relativedelta(arg1=x,arg2=y,arg3=z...)
+
+        year, month, day, hour, minute, second, microsecond:
+            Absolute information (argument is singular); adding or subtracting a
+            relativedelta with absolute information does not perform an arithmetic
+            operation, but rather REPLACES the corresponding value in the
+            original datetime with the value(s) in relativedelta.
+
+        years, months, weeks, days, hours, minutes, seconds, microseconds:
+            Relative information, may be negative (argument is plural); adding
+            or subtracting a relativedelta with relative information performs
+            the corresponding arithmetic operation on the original datetime value
+            with the information in the relativedelta.
+
+        weekday: 
+            One of the weekday instances (MO, TU, etc) available in the
+            relativedelta module. These instances may receive a parameter N,
+            specifying the Nth weekday, which could be positive or negative
+            (like MO(+1) or MO(-2)). Not specifying it is the same as specifying
+            +1. You can also use an integer, where 0=MO. This argument is always
+            relative e.g. if the calculated date is already Monday, using MO(1)
+            or MO(-1) won't change the day. To effectively make it absolute, use
+            it in combination with the day argument (e.g. day=1, MO(1) for first
+            Monday of the month).
+
+        leapdays:
+            Will add given days to the date found, if year is a leap
+            year, and the date found is post 28 of february.
+
+        yearday, nlyearday:
+            Set the yearday or the non-leap year day (jump leap days).
+            These are converted to day/month/leapdays information.
+
+    There are relative and absolute forms of the keyword
+    arguments. The plural is relative, and the singular is
+    absolute. For each argument in the order below, the absolute form
+    is applied first (by setting each attribute to that value) and
+    then the relative form (by adding the value to the attribute).
+
+    The order of attributes considered when this relativedelta is
+    added to a datetime is:
+
+    1. Year
+    2. Month
+    3. Day
+    4. Hours
+    5. Minutes
+    6. Seconds
+    7. Microseconds
+
+    Finally, weekday is applied, using the rule described above.
+
+    For example
+
+    >>> from datetime import datetime
+    >>> from dateutil.relativedelta import relativedelta, MO
+    >>> dt = datetime(2018, 4, 9, 13, 37, 0)
+    >>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
+    >>> dt + delta
+    datetime.datetime(2018, 4, 2, 14, 37)
+
+    First, the day is set to 1 (the first of the month), then 25 hours
+    are added, to get to the 2nd day and 14th hour, finally the
+    weekday is applied, but since the 2nd is already a Monday there is
+    no effect.
+
+    """
+
+    def __init__(self, dt1=None, dt2=None,
+                 years=0, months=0, days=0, leapdays=0, weeks=0,
+                 hours=0, minutes=0, seconds=0, microseconds=0,
+                 year=None, month=None, day=None, weekday=None,
+                 yearday=None, nlyearday=None,
+                 hour=None, minute=None, second=None, microsecond=None):
+
+        if dt1 and dt2:
+            # datetime is a subclass of date. So both must be date
+            if not (isinstance(dt1, datetime.date) and
+                    isinstance(dt2, datetime.date)):
+                raise TypeError("relativedelta only diffs datetime/date")
+
+            # We allow two dates, or two datetimes, so we coerce them to be
+            # of the same type
+            if (isinstance(dt1, datetime.datetime) !=
+                    isinstance(dt2, datetime.datetime)):
+                if not isinstance(dt1, datetime.datetime):
+                    dt1 = datetime.datetime.fromordinal(dt1.toordinal())
+                elif not isinstance(dt2, datetime.datetime):
+                    dt2 = datetime.datetime.fromordinal(dt2.toordinal())
+
+            self.years = 0
+            self.months = 0
+            self.days = 0
+            self.leapdays = 0
+            self.hours = 0
+            self.minutes = 0
+            self.seconds = 0
+            self.microseconds = 0
+            self.year = None
+            self.month = None
+            self.day = None
+            self.weekday = None
+            self.hour = None
+            self.minute = None
+            self.second = None
+            self.microsecond = None
+            self._has_time = 0
+
+            # Get year / month delta between the two
+            months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
+            self._set_months(months)
+
+            # Remove the year/month delta so the timedelta is just well-defined
+            # time units (seconds, days and microseconds)
+            dtm = self.__radd__(dt2)
+
+            # If we've overshot our target, make an adjustment
+            if dt1 < dt2:
+                compare = operator.gt
+                increment = 1
+            else:
+                compare = operator.lt
+                increment = -1
+
+            while compare(dt1, dtm):
+                months += increment
+                self._set_months(months)
+                dtm = self.__radd__(dt2)
+
+            # Get the timedelta between the "months-adjusted" date and dt1
+            delta = dt1 - dtm
+            self.seconds = delta.seconds + delta.days * 86400
+            self.microseconds = delta.microseconds
+        else:
+            # Check for non-integer values in integer-only quantities
+            if any(x is not None and x != int(x) for x in (years, months)):
+                raise ValueError("Non-integer years and months are "
+                                 "ambiguous and not currently supported.")
+
+            # Relative information
+            self.years = int(years)
+            self.months = int(months)
+            self.days = days + weeks * 7
+            self.leapdays = leapdays
+            self.hours = hours
+            self.minutes = minutes
+            self.seconds = seconds
+            self.microseconds = microseconds
+
+            # Absolute information
+            self.year = year
+            self.month = month
+            self.day = day
+            self.hour = hour
+            self.minute = minute
+            self.second = second
+            self.microsecond = microsecond
+
+            if any(x is not None and int(x) != x
+                   for x in (year, month, day, hour,
+                             minute, second, microsecond)):
+                # For now we'll deprecate floats - later it'll be an error.
+                warn("Non-integer value passed as absolute information. " +
+                     "This is not a well-defined condition and will raise " +
+                     "errors in future versions.", DeprecationWarning)
+
+            if isinstance(weekday, integer_types):
+                self.weekday = weekdays[weekday]
+            else:
+                self.weekday = weekday
+
+            yday = 0
+            if nlyearday:
+                yday = nlyearday
+            elif yearday:
+                yday = yearday
+                if yearday > 59:
+                    self.leapdays = -1
+            if yday:
+                ydayidx = [31, 59, 90, 120, 151, 181, 212,
+                           243, 273, 304, 334, 366]
+                for idx, ydays in enumerate(ydayidx):
+                    if yday <= ydays:
+                        self.month = idx+1
+                        if idx == 0:
+                            self.day = yday
+                        else:
+                            self.day = yday-ydayidx[idx-1]
+                        break
+                else:
+                    raise ValueError("invalid year day (%d)" % yday)
+
+        self._fix()
+
+    def _fix(self):
+        if abs(self.microseconds) > 999999:
+            s = _sign(self.microseconds)
+            div, mod = divmod(self.microseconds * s, 1000000)
+            self.microseconds = mod * s
+            self.seconds += div * s
+        if abs(self.seconds) > 59:
+            s = _sign(self.seconds)
+            div, mod = divmod(self.seconds * s, 60)
+            self.seconds = mod * s
+            self.minutes += div * s
+        if abs(self.minutes) > 59:
+            s = _sign(self.minutes)
+            div, mod = divmod(self.minutes * s, 60)
+            self.minutes = mod * s
+            self.hours += div * s
+        if abs(self.hours) > 23:
+            s = _sign(self.hours)
+            div, mod = divmod(self.hours * s, 24)
+            self.hours = mod * s
+            self.days += div * s
+        if abs(self.months) > 11:
+            s = _sign(self.months)
+            div, mod = divmod(self.months * s, 12)
+            self.months = mod * s
+            self.years += div * s
+        if (self.hours or self.minutes or self.seconds or self.microseconds
+                or self.hour is not None or self.minute is not None or
+                self.second is not None or self.microsecond is not None):
+            self._has_time = 1
+        else:
+            self._has_time = 0
+
+    @property
+    def weeks(self):
+        return int(self.days / 7.0)
+
+    @weeks.setter
+    def weeks(self, value):
+        self.days = self.days - (self.weeks * 7) + value * 7
+
+    def _set_months(self, months):
+        self.months = months
+        if abs(self.months) > 11:
+            s = _sign(self.months)
+            div, mod = divmod(self.months * s, 12)
+            self.months = mod * s
+            self.years = div * s
+        else:
+            self.years = 0
+
+    def normalized(self):
+        """
+        Return a version of this object represented entirely using integer
+        values for the relative attributes.
+
+        >>> relativedelta(days=1.5, hours=2).normalized()
+        relativedelta(days=+1, hours=+14)
+
+        :return:
+            Returns a :class:`dateutil.relativedelta.relativedelta` object.
+        """
+        # Cascade remainders down (rounding each to roughly nearest microsecond)
+        days = int(self.days)
+
+        hours_f = round(self.hours + 24 * (self.days - days), 11)
+        hours = int(hours_f)
+
+        minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
+        minutes = int(minutes_f)
+
+        seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
+        seconds = int(seconds_f)
+
+        microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
+
+        # Constructor carries overflow back up with call to _fix()
+        return self.__class__(years=self.years, months=self.months,
+                              days=days, hours=hours, minutes=minutes,
+                              seconds=seconds, microseconds=microseconds,
+                              leapdays=self.leapdays, year=self.year,
+                              month=self.month, day=self.day,
+                              weekday=self.weekday, hour=self.hour,
+                              minute=self.minute, second=self.second,
+                              microsecond=self.microsecond)
+
+    def __add__(self, other):
+        if isinstance(other, relativedelta):
+            return self.__class__(years=other.years + self.years,
+                                 months=other.months + self.months,
+                                 days=other.days + self.days,
+                                 hours=other.hours + self.hours,
+                                 minutes=other.minutes + self.minutes,
+                                 seconds=other.seconds + self.seconds,
+                                 microseconds=(other.microseconds +
+                                               self.microseconds),
+                                 leapdays=other.leapdays or self.leapdays,
+                                 year=(other.year if other.year is not None
+                                       else self.year),
+                                 month=(other.month if other.month is not None
+                                        else self.month),
+                                 day=(other.day if other.day is not None
+                                      else self.day),
+                                 weekday=(other.weekday if other.weekday is not None
+                                          else self.weekday),
+                                 hour=(other.hour if other.hour is not None
+                                       else self.hour),
+                                 minute=(other.minute if other.minute is not None
+                                         else self.minute),
+                                 second=(other.second if other.second is not None
+                                         else self.second),
+                                 microsecond=(other.microsecond if other.microsecond
+                                              is not None else
+                                              self.microsecond))
+        if isinstance(other, datetime.timedelta):
+            return self.__class__(years=self.years,
+                                  months=self.months,
+                                  days=self.days + other.days,
+                                  hours=self.hours,
+                                  minutes=self.minutes,
+                                  seconds=self.seconds + other.seconds,
+                                  microseconds=self.microseconds + other.microseconds,
+                                  leapdays=self.leapdays,
+                                  year=self.year,
+                                  month=self.month,
+                                  day=self.day,
+                                  weekday=self.weekday,
+                                  hour=self.hour,
+                                  minute=self.minute,
+                                  second=self.second,
+                                  microsecond=self.microsecond)
+        if not isinstance(other, datetime.date):
+            return NotImplemented
+        elif self._has_time and not isinstance(other, datetime.datetime):
+            other = datetime.datetime.fromordinal(other.toordinal())
+        year = (self.year or other.year)+self.years
+        month = self.month or other.month
+        if self.months:
+            assert 1 <= abs(self.months) <= 12
+            month += self.months
+            if month > 12:
+                year += 1
+                month -= 12
+            elif month < 1:
+                year -= 1
+                month += 12
+        day = min(calendar.monthrange(year, month)[1],
+                  self.day or other.day)
+        repl = {"year": year, "month": month, "day": day}
+        for attr in ["hour", "minute", "second", "microsecond"]:
+            value = getattr(self, attr)
+            if value is not None:
+                repl[attr] = value
+        days = self.days
+        if self.leapdays and month > 2 and calendar.isleap(year):
+            days += self.leapdays
+        ret = (other.replace(**repl)
+               + datetime.timedelta(days=days,
+                                    hours=self.hours,
+                                    minutes=self.minutes,
+                                    seconds=self.seconds,
+                                    microseconds=self.microseconds))
+        if self.weekday:
+            weekday, nth = self.weekday.weekday, self.weekday.n or 1
+            jumpdays = (abs(nth) - 1) * 7
+            if nth > 0:
+                jumpdays += (7 - ret.weekday() + weekday) % 7
+            else:
+                jumpdays += (ret.weekday() - weekday) % 7
+                jumpdays *= -1
+            ret += datetime.timedelta(days=jumpdays)
+        return ret
+
+    def __radd__(self, other):
+        return self.__add__(other)
+
+    def __rsub__(self, other):
+        return self.__neg__().__radd__(other)
+
+    def __sub__(self, other):
+        if not isinstance(other, relativedelta):
+            return NotImplemented   # In case the other object defines __rsub__
+        return self.__class__(years=self.years - other.years,
+                             months=self.months - other.months,
+                             days=self.days - other.days,
+                             hours=self.hours - other.hours,
+                             minutes=self.minutes - other.minutes,
+                             seconds=self.seconds - other.seconds,
+                             microseconds=self.microseconds - other.microseconds,
+                             leapdays=self.leapdays or other.leapdays,
+                             year=(self.year if self.year is not None
+                                   else other.year),
+                             month=(self.month if self.month is not None else
+                                    other.month),
+                             day=(self.day if self.day is not None else
+                                  other.day),
+                             weekday=(self.weekday if self.weekday is not None else
+                                      other.weekday),
+                             hour=(self.hour if self.hour is not None else
+                                   other.hour),
+                             minute=(self.minute if self.minute is not None else
+                                     other.minute),
+                             second=(self.second if self.second is not None else
+                                     other.second),
+                             microsecond=(self.microsecond if self.microsecond
+                                          is not None else
+                                          other.microsecond))
+
+    def __abs__(self):
+        return self.__class__(years=abs(self.years),
+                              months=abs(self.months),
+                              days=abs(self.days),
+                              hours=abs(self.hours),
+                              minutes=abs(self.minutes),
+                              seconds=abs(self.seconds),
+                              microseconds=abs(self.microseconds),
+                              leapdays=self.leapdays,
+                              year=self.year,
+                              month=self.month,
+                              day=self.day,
+                              weekday=self.weekday,
+                              hour=self.hour,
+                              minute=self.minute,
+                              second=self.second,
+                              microsecond=self.microsecond)
+
+    def __neg__(self):
+        return self.__class__(years=-self.years,
+                             months=-self.months,
+                             days=-self.days,
+                             hours=-self.hours,
+                             minutes=-self.minutes,
+                             seconds=-self.seconds,
+                             microseconds=-self.microseconds,
+                             leapdays=self.leapdays,
+                             year=self.year,
+                             month=self.month,
+                             day=self.day,
+                             weekday=self.weekday,
+                             hour=self.hour,
+                             minute=self.minute,
+                             second=self.second,
+                             microsecond=self.microsecond)
+
+    def __bool__(self):
+        return not (not self.years and
+                    not self.months and
+                    not self.days and
+                    not self.hours and
+                    not self.minutes and
+                    not self.seconds and
+                    not self.microseconds and
+                    not self.leapdays and
+                    self.year is None and
+                    self.month is None and
+                    self.day is None and
+                    self.weekday is None and
+                    self.hour is None and
+                    self.minute is None and
+                    self.second is None and
+                    self.microsecond is None)
+    # Compatibility with Python 2.x
+    __nonzero__ = __bool__
+
+    def __mul__(self, other):
+        try:
+            f = float(other)
+        except TypeError:
+            return NotImplemented
+
+        return self.__class__(years=int(self.years * f),
+                             months=int(self.months * f),
+                             days=int(self.days * f),
+                             hours=int(self.hours * f),
+                             minutes=int(self.minutes * f),
+                             seconds=int(self.seconds * f),
+                             microseconds=int(self.microseconds * f),
+                             leapdays=self.leapdays,
+                             year=self.year,
+                             month=self.month,
+                             day=self.day,
+                             weekday=self.weekday,
+                             hour=self.hour,
+                             minute=self.minute,
+                             second=self.second,
+                             microsecond=self.microsecond)
+
+    __rmul__ = __mul__
+
+    def __eq__(self, other):
+        if not isinstance(other, relativedelta):
+            return NotImplemented
+        if self.weekday or other.weekday:
+            if not self.weekday or not other.weekday:
+                return False
+            if self.weekday.weekday != other.weekday.weekday:
+                return False
+            n1, n2 = self.weekday.n, other.weekday.n
+            if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
+                return False
+        return (self.years == other.years and
+                self.months == other.months and
+                self.days == other.days and
+                self.hours == other.hours and
+                self.minutes == other.minutes and
+                self.seconds == other.seconds and
+                self.microseconds == other.microseconds and
+                self.leapdays == other.leapdays and
+                self.year == other.year and
+                self.month == other.month and
+                self.day == other.day and
+                self.hour == other.hour and
+                self.minute == other.minute and
+                self.second == other.second and
+                self.microsecond == other.microsecond)
+
+    def __hash__(self):
+        return hash((
+            self.weekday,
+            self.years,
+            self.months,
+            self.days,
+            self.hours,
+            self.minutes,
+            self.seconds,
+            self.microseconds,
+            self.leapdays,
+            self.year,
+            self.month,
+            self.day,
+            self.hour,
+            self.minute,
+            self.second,
+            self.microsecond,
+        ))
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __div__(self, other):
+        try:
+            reciprocal = 1 / float(other)
+        except TypeError:
+            return NotImplemented
+
+        return self.__mul__(reciprocal)
+
+    __truediv__ = __div__
+
+    def __repr__(self):
+        l = []
+        for attr in ["years", "months", "days", "leapdays",
+                     "hours", "minutes", "seconds", "microseconds"]:
+            value = getattr(self, attr)
+            if value:
+                l.append("{attr}={value:+g}".format(attr=attr, value=value))
+        for attr in ["year", "month", "day", "weekday",
+                     "hour", "minute", "second", "microsecond"]:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("{attr}={value}".format(attr=attr, value=repr(value)))
+        return "{classname}({attrs})".format(classname=self.__class__.__name__,
+                                             attrs=", ".join(l))
+
+
+def _sign(x):
+    return int(copysign(1, x))
+
+# vim:ts=4:sw=4:et

+ 1735 - 0
venv/lib/python3.8/site-packages/dateutil/rrule.py

@@ -0,0 +1,1735 @@
+# -*- coding: utf-8 -*-
+"""
+The rrule module offers a small, complete, and very fast, implementation of
+the recurrence rules documented in the
+`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,
+including support for caching of results.
+"""
+import itertools
+import datetime
+import calendar
+import re
+import sys
+
+try:
+    from math import gcd
+except ImportError:
+    from fractions import gcd
+
+from six import advance_iterator, integer_types
+from six.moves import _thread, range
+import heapq
+
+from ._common import weekday as weekdaybase
+
+# For warning about deprecation of until and count
+from warnings import warn
+
+__all__ = ["rrule", "rruleset", "rrulestr",
+           "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
+           "HOURLY", "MINUTELY", "SECONDLY",
+           "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
+
+# Every mask is 7 days longer to handle cross-year weekly periods.
+M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +
+                 [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
+M365MASK = list(M366MASK)
+M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))
+MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
+MDAY365MASK = list(MDAY366MASK)
+M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))
+NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
+NMDAY365MASK = list(NMDAY366MASK)
+M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)
+M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
+WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55
+del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
+MDAY365MASK = tuple(MDAY365MASK)
+M365MASK = tuple(M365MASK)
+
+FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']
+
+(YEARLY,
+ MONTHLY,
+ WEEKLY,
+ DAILY,
+ HOURLY,
+ MINUTELY,
+ SECONDLY) = list(range(7))
+
+# Imported on demand.
+easter = None
+parser = None
+
+
+class weekday(weekdaybase):
+    """
+    This version of weekday does not allow n = 0.
+    """
+    def __init__(self, wkday, n=None):
+        if n == 0:
+            raise ValueError("Can't create weekday with n==0")
+
+        super(weekday, self).__init__(wkday, n)
+
+
+MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
+
+
+def _invalidates_cache(f):
+    """
+    Decorator for rruleset methods which may invalidate the
+    cached length.
+    """
+    def inner_func(self, *args, **kwargs):
+        rv = f(self, *args, **kwargs)
+        self._invalidate_cache()
+        return rv
+
+    return inner_func
+
+
+class rrulebase(object):
+    def __init__(self, cache=False):
+        if cache:
+            self._cache = []
+            self._cache_lock = _thread.allocate_lock()
+            self._invalidate_cache()
+        else:
+            self._cache = None
+            self._cache_complete = False
+            self._len = None
+
+    def __iter__(self):
+        if self._cache_complete:
+            return iter(self._cache)
+        elif self._cache is None:
+            return self._iter()
+        else:
+            return self._iter_cached()
+
+    def _invalidate_cache(self):
+        if self._cache is not None:
+            self._cache = []
+            self._cache_complete = False
+            self._cache_gen = self._iter()
+
+            if self._cache_lock.locked():
+                self._cache_lock.release()
+
+        self._len = None
+
+    def _iter_cached(self):
+        i = 0
+        gen = self._cache_gen
+        cache = self._cache
+        acquire = self._cache_lock.acquire
+        release = self._cache_lock.release
+        while gen:
+            if i == len(cache):
+                acquire()
+                if self._cache_complete:
+                    break
+                try:
+                    for j in range(10):
+                        cache.append(advance_iterator(gen))
+                except StopIteration:
+                    self._cache_gen = gen = None
+                    self._cache_complete = True
+                    break
+                release()
+            yield cache[i]
+            i += 1
+        while i < self._len:
+            yield cache[i]
+            i += 1
+
+    def __getitem__(self, item):
+        if self._cache_complete:
+            return self._cache[item]
+        elif isinstance(item, slice):
+            if item.step and item.step < 0:
+                return list(iter(self))[item]
+            else:
+                return list(itertools.islice(self,
+                                             item.start or 0,
+                                             item.stop or sys.maxsize,
+                                             item.step or 1))
+        elif item >= 0:
+            gen = iter(self)
+            try:
+                for i in range(item+1):
+                    res = advance_iterator(gen)
+            except StopIteration:
+                raise IndexError
+            return res
+        else:
+            return list(iter(self))[item]
+
+    def __contains__(self, item):
+        if self._cache_complete:
+            return item in self._cache
+        else:
+            for i in self:
+                if i == item:
+                    return True
+                elif i > item:
+                    return False
+        return False
+
+    # __len__() introduces a large performance penalty.
+    def count(self):
+        """ Returns the number of recurrences in this set. It will have go
+            trough the whole recurrence, if this hasn't been done before. """
+        if self._len is None:
+            for x in self:
+                pass
+        return self._len
+
+    def before(self, dt, inc=False):
+        """ Returns the last recurrence before the given datetime instance. The
+            inc keyword defines what happens if dt is an occurrence. With
+            inc=True, if dt itself is an occurrence, it will be returned. """
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+        last = None
+        if inc:
+            for i in gen:
+                if i > dt:
+                    break
+                last = i
+        else:
+            for i in gen:
+                if i >= dt:
+                    break
+                last = i
+        return last
+
+    def after(self, dt, inc=False):
+        """ Returns the first recurrence after the given datetime instance. The
+            inc keyword defines what happens if dt is an occurrence. With
+            inc=True, if dt itself is an occurrence, it will be returned.  """
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+        if inc:
+            for i in gen:
+                if i >= dt:
+                    return i
+        else:
+            for i in gen:
+                if i > dt:
+                    return i
+        return None
+
+    def xafter(self, dt, count=None, inc=False):
+        """
+        Generator which yields up to `count` recurrences after the given
+        datetime instance, equivalent to `after`.
+
+        :param dt:
+            The datetime at which to start generating recurrences.
+
+        :param count:
+            The maximum number of recurrences to generate. If `None` (default),
+            dates are generated until the recurrence rule is exhausted.
+
+        :param inc:
+            If `dt` is an instance of the rule and `inc` is `True`, it is
+            included in the output.
+
+        :yields: Yields a sequence of `datetime` objects.
+        """
+
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+
+        # Select the comparison function
+        if inc:
+            comp = lambda dc, dtc: dc >= dtc
+        else:
+            comp = lambda dc, dtc: dc > dtc
+
+        # Generate dates
+        n = 0
+        for d in gen:
+            if comp(d, dt):
+                if count is not None:
+                    n += 1
+                    if n > count:
+                        break
+
+                yield d
+
+    def between(self, after, before, inc=False, count=1):
+        """ Returns all the occurrences of the rrule between after and before.
+        The inc keyword defines what happens if after and/or before are
+        themselves occurrences. With inc=True, they will be included in the
+        list, if they are found in the recurrence set. """
+        if self._cache_complete:
+            gen = self._cache
+        else:
+            gen = self
+        started = False
+        l = []
+        if inc:
+            for i in gen:
+                if i > before:
+                    break
+                elif not started:
+                    if i >= after:
+                        started = True
+                        l.append(i)
+                else:
+                    l.append(i)
+        else:
+            for i in gen:
+                if i >= before:
+                    break
+                elif not started:
+                    if i > after:
+                        started = True
+                        l.append(i)
+                else:
+                    l.append(i)
+        return l
+
+
+class rrule(rrulebase):
+    """
+    That's the base of the rrule operation. It accepts all the keywords
+    defined in the RFC as its constructor parameters (except byday,
+    which was renamed to byweekday) and more. The constructor prototype is::
+
+            rrule(freq)
+
+    Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
+    or SECONDLY.
+
+    .. note::
+        Per RFC section 3.3.10, recurrence instances falling on invalid dates
+        and times are ignored rather than coerced:
+
+            Recurrence rules may generate recurrence instances with an invalid
+            date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
+            on a day where the local time is moved forward by an hour at 1:00
+            AM).  Such recurrence instances MUST be ignored and MUST NOT be
+            counted as part of the recurrence set.
+
+        This can lead to possibly surprising behavior when, for example, the
+        start date occurs at the end of the month:
+
+        >>> from dateutil.rrule import rrule, MONTHLY
+        >>> from datetime import datetime
+        >>> start_date = datetime(2014, 12, 31)
+        >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))
+        ... # doctest: +NORMALIZE_WHITESPACE
+        [datetime.datetime(2014, 12, 31, 0, 0),
+         datetime.datetime(2015, 1, 31, 0, 0),
+         datetime.datetime(2015, 3, 31, 0, 0),
+         datetime.datetime(2015, 5, 31, 0, 0)]
+
+    Additionally, it supports the following keyword arguments:
+
+    :param dtstart:
+        The recurrence start. Besides being the base for the recurrence,
+        missing parameters in the final recurrence instances will also be
+        extracted from this date. If not given, datetime.now() will be used
+        instead.
+    :param interval:
+        The interval between each freq iteration. For example, when using
+        YEARLY, an interval of 2 means once every two years, but with HOURLY,
+        it means once every two hours. The default interval is 1.
+    :param wkst:
+        The week start day. Must be one of the MO, TU, WE constants, or an
+        integer, specifying the first day of the week. This will affect
+        recurrences based on weekly periods. The default week start is got
+        from calendar.firstweekday(), and may be modified by
+        calendar.setfirstweekday().
+    :param count:
+        If given, this determines how many occurrences will be generated.
+
+        .. note::
+            As of version 2.5.0, the use of the keyword ``until`` in conjunction
+            with ``count`` is deprecated, to make sure ``dateutil`` is fully
+            compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
+            html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
+            **must not** occur in the same call to ``rrule``.
+    :param until:
+        If given, this must be a datetime instance specifying the upper-bound
+        limit of the recurrence. The last recurrence in the rule is the greatest
+        datetime that is less than or equal to the value specified in the
+        ``until`` parameter.
+
+        .. note::
+            As of version 2.5.0, the use of the keyword ``until`` in conjunction
+            with ``count`` is deprecated, to make sure ``dateutil`` is fully
+            compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
+            html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
+            **must not** occur in the same call to ``rrule``.
+    :param bysetpos:
+        If given, it must be either an integer, or a sequence of integers,
+        positive or negative. Each given integer will specify an occurrence
+        number, corresponding to the nth occurrence of the rule inside the
+        frequency period. For example, a bysetpos of -1 if combined with a
+        MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will
+        result in the last work day of every month.
+    :param bymonth:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the months to apply the recurrence to.
+    :param bymonthday:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the month days to apply the recurrence to.
+    :param byyearday:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the year days to apply the recurrence to.
+    :param byeaster:
+        If given, it must be either an integer, or a sequence of integers,
+        positive or negative. Each integer will define an offset from the
+        Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
+        Sunday itself. This is an extension to the RFC specification.
+    :param byweekno:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the week numbers to apply the recurrence to. Week numbers
+        have the meaning described in ISO8601, that is, the first week of
+        the year is that containing at least four days of the new year.
+    :param byweekday:
+        If given, it must be either an integer (0 == MO), a sequence of
+        integers, one of the weekday constants (MO, TU, etc), or a sequence
+        of these constants. When given, these variables will define the
+        weekdays where the recurrence will be applied. It's also possible to
+        use an argument n for the weekday instances, which will mean the nth
+        occurrence of this weekday in the period. For example, with MONTHLY,
+        or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the
+        first friday of the month where the recurrence happens. Notice that in
+        the RFC documentation, this is specified as BYDAY, but was renamed to
+        avoid the ambiguity of that keyword.
+    :param byhour:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the hours to apply the recurrence to.
+    :param byminute:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the minutes to apply the recurrence to.
+    :param bysecond:
+        If given, it must be either an integer, or a sequence of integers,
+        meaning the seconds to apply the recurrence to.
+    :param cache:
+        If given, it must be a boolean value specifying to enable or disable
+        caching of results. If you will use the same rrule instance multiple
+        times, enabling caching will improve the performance considerably.
+     """
+    def __init__(self, freq, dtstart=None,
+                 interval=1, wkst=None, count=None, until=None, bysetpos=None,
+                 bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
+                 byweekno=None, byweekday=None,
+                 byhour=None, byminute=None, bysecond=None,
+                 cache=False):
+        super(rrule, self).__init__(cache)
+        global easter
+        if not dtstart:
+            if until and until.tzinfo:
+                dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
+            else:
+                dtstart = datetime.datetime.now().replace(microsecond=0)
+        elif not isinstance(dtstart, datetime.datetime):
+            dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
+        else:
+            dtstart = dtstart.replace(microsecond=0)
+        self._dtstart = dtstart
+        self._tzinfo = dtstart.tzinfo
+        self._freq = freq
+        self._interval = interval
+        self._count = count
+
+        # Cache the original byxxx rules, if they are provided, as the _byxxx
+        # attributes do not necessarily map to the inputs, and this can be
+        # a problem in generating the strings. Only store things if they've
+        # been supplied (the string retrieval will just use .get())
+        self._original_rule = {}
+
+        if until and not isinstance(until, datetime.datetime):
+            until = datetime.datetime.fromordinal(until.toordinal())
+        self._until = until
+
+        if self._dtstart and self._until:
+            if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):
+                # According to RFC5545 Section 3.3.10:
+                # https://tools.ietf.org/html/rfc5545#section-3.3.10
+                #
+                # > If the "DTSTART" property is specified as a date with UTC
+                # > time or a date with local time and time zone reference,
+                # > then the UNTIL rule part MUST be specified as a date with
+                # > UTC time.
+                raise ValueError(
+                    'RRULE UNTIL values must be specified in UTC when DTSTART '
+                    'is timezone-aware'
+                )
+
+        if count is not None and until:
+            warn("Using both 'count' and 'until' is inconsistent with RFC 5545"
+                 " and has been deprecated in dateutil. Future versions will "
+                 "raise an error.", DeprecationWarning)
+
+        if wkst is None:
+            self._wkst = calendar.firstweekday()
+        elif isinstance(wkst, integer_types):
+            self._wkst = wkst
+        else:
+            self._wkst = wkst.weekday
+
+        if bysetpos is None:
+            self._bysetpos = None
+        elif isinstance(bysetpos, integer_types):
+            if bysetpos == 0 or not (-366 <= bysetpos <= 366):
+                raise ValueError("bysetpos must be between 1 and 366, "
+                                 "or between -366 and -1")
+            self._bysetpos = (bysetpos,)
+        else:
+            self._bysetpos = tuple(bysetpos)
+            for pos in self._bysetpos:
+                if pos == 0 or not (-366 <= pos <= 366):
+                    raise ValueError("bysetpos must be between 1 and 366, "
+                                     "or between -366 and -1")
+
+        if self._bysetpos:
+            self._original_rule['bysetpos'] = self._bysetpos
+
+        if (byweekno is None and byyearday is None and bymonthday is None and
+                byweekday is None and byeaster is None):
+            if freq == YEARLY:
+                if bymonth is None:
+                    bymonth = dtstart.month
+                    self._original_rule['bymonth'] = None
+                bymonthday = dtstart.day
+                self._original_rule['bymonthday'] = None
+            elif freq == MONTHLY:
+                bymonthday = dtstart.day
+                self._original_rule['bymonthday'] = None
+            elif freq == WEEKLY:
+                byweekday = dtstart.weekday()
+                self._original_rule['byweekday'] = None
+
+        # bymonth
+        if bymonth is None:
+            self._bymonth = None
+        else:
+            if isinstance(bymonth, integer_types):
+                bymonth = (bymonth,)
+
+            self._bymonth = tuple(sorted(set(bymonth)))
+
+            if 'bymonth' not in self._original_rule:
+                self._original_rule['bymonth'] = self._bymonth
+
+        # byyearday
+        if byyearday is None:
+            self._byyearday = None
+        else:
+            if isinstance(byyearday, integer_types):
+                byyearday = (byyearday,)
+
+            self._byyearday = tuple(sorted(set(byyearday)))
+            self._original_rule['byyearday'] = self._byyearday
+
+        # byeaster
+        if byeaster is not None:
+            if not easter:
+                from dateutil import easter
+            if isinstance(byeaster, integer_types):
+                self._byeaster = (byeaster,)
+            else:
+                self._byeaster = tuple(sorted(byeaster))
+
+            self._original_rule['byeaster'] = self._byeaster
+        else:
+            self._byeaster = None
+
+        # bymonthday
+        if bymonthday is None:
+            self._bymonthday = ()
+            self._bynmonthday = ()
+        else:
+            if isinstance(bymonthday, integer_types):
+                bymonthday = (bymonthday,)
+
+            bymonthday = set(bymonthday)            # Ensure it's unique
+
+            self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))
+            self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))
+
+            # Storing positive numbers first, then negative numbers
+            if 'bymonthday' not in self._original_rule:
+                self._original_rule['bymonthday'] = tuple(
+                    itertools.chain(self._bymonthday, self._bynmonthday))
+
+        # byweekno
+        if byweekno is None:
+            self._byweekno = None
+        else:
+            if isinstance(byweekno, integer_types):
+                byweekno = (byweekno,)
+
+            self._byweekno = tuple(sorted(set(byweekno)))
+
+            self._original_rule['byweekno'] = self._byweekno
+
+        # byweekday / bynweekday
+        if byweekday is None:
+            self._byweekday = None
+            self._bynweekday = None
+        else:
+            # If it's one of the valid non-sequence types, convert to a
+            # single-element sequence before the iterator that builds the
+            # byweekday set.
+            if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):
+                byweekday = (byweekday,)
+
+            self._byweekday = set()
+            self._bynweekday = set()
+            for wday in byweekday:
+                if isinstance(wday, integer_types):
+                    self._byweekday.add(wday)
+                elif not wday.n or freq > MONTHLY:
+                    self._byweekday.add(wday.weekday)
+                else:
+                    self._bynweekday.add((wday.weekday, wday.n))
+
+            if not self._byweekday:
+                self._byweekday = None
+            elif not self._bynweekday:
+                self._bynweekday = None
+
+            if self._byweekday is not None:
+                self._byweekday = tuple(sorted(self._byweekday))
+                orig_byweekday = [weekday(x) for x in self._byweekday]
+            else:
+                orig_byweekday = ()
+
+            if self._bynweekday is not None:
+                self._bynweekday = tuple(sorted(self._bynweekday))
+                orig_bynweekday = [weekday(*x) for x in self._bynweekday]
+            else:
+                orig_bynweekday = ()
+
+            if 'byweekday' not in self._original_rule:
+                self._original_rule['byweekday'] = tuple(itertools.chain(
+                    orig_byweekday, orig_bynweekday))
+
+        # byhour
+        if byhour is None:
+            if freq < HOURLY:
+                self._byhour = {dtstart.hour}
+            else:
+                self._byhour = None
+        else:
+            if isinstance(byhour, integer_types):
+                byhour = (byhour,)
+
+            if freq == HOURLY:
+                self._byhour = self.__construct_byset(start=dtstart.hour,
+                                                      byxxx=byhour,
+                                                      base=24)
+            else:
+                self._byhour = set(byhour)
+
+            self._byhour = tuple(sorted(self._byhour))
+            self._original_rule['byhour'] = self._byhour
+
+        # byminute
+        if byminute is None:
+            if freq < MINUTELY:
+                self._byminute = {dtstart.minute}
+            else:
+                self._byminute = None
+        else:
+            if isinstance(byminute, integer_types):
+                byminute = (byminute,)
+
+            if freq == MINUTELY:
+                self._byminute = self.__construct_byset(start=dtstart.minute,
+                                                        byxxx=byminute,
+                                                        base=60)
+            else:
+                self._byminute = set(byminute)
+
+            self._byminute = tuple(sorted(self._byminute))
+            self._original_rule['byminute'] = self._byminute
+
+        # bysecond
+        if bysecond is None:
+            if freq < SECONDLY:
+                self._bysecond = ((dtstart.second,))
+            else:
+                self._bysecond = None
+        else:
+            if isinstance(bysecond, integer_types):
+                bysecond = (bysecond,)
+
+            self._bysecond = set(bysecond)
+
+            if freq == SECONDLY:
+                self._bysecond = self.__construct_byset(start=dtstart.second,
+                                                        byxxx=bysecond,
+                                                        base=60)
+            else:
+                self._bysecond = set(bysecond)
+
+            self._bysecond = tuple(sorted(self._bysecond))
+            self._original_rule['bysecond'] = self._bysecond
+
+        if self._freq >= HOURLY:
+            self._timeset = None
+        else:
+            self._timeset = []
+            for hour in self._byhour:
+                for minute in self._byminute:
+                    for second in self._bysecond:
+                        self._timeset.append(
+                            datetime.time(hour, minute, second,
+                                          tzinfo=self._tzinfo))
+            self._timeset.sort()
+            self._timeset = tuple(self._timeset)
+
+    def __str__(self):
+        """
+        Output a string that would generate this RRULE if passed to rrulestr.
+        This is mostly compatible with RFC5545, except for the
+        dateutil-specific extension BYEASTER.
+        """
+
+        output = []
+        h, m, s = [None] * 3
+        if self._dtstart:
+            output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))
+            h, m, s = self._dtstart.timetuple()[3:6]
+
+        parts = ['FREQ=' + FREQNAMES[self._freq]]
+        if self._interval != 1:
+            parts.append('INTERVAL=' + str(self._interval))
+
+        if self._wkst:
+            parts.append('WKST=' + repr(weekday(self._wkst))[0:2])
+
+        if self._count is not None:
+            parts.append('COUNT=' + str(self._count))
+
+        if self._until:
+            parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))
+
+        if self._original_rule.get('byweekday') is not None:
+            # The str() method on weekday objects doesn't generate
+            # RFC5545-compliant strings, so we should modify that.
+            original_rule = dict(self._original_rule)
+            wday_strings = []
+            for wday in original_rule['byweekday']:
+                if wday.n:
+                    wday_strings.append('{n:+d}{wday}'.format(
+                        n=wday.n,
+                        wday=repr(wday)[0:2]))
+                else:
+                    wday_strings.append(repr(wday))
+
+            original_rule['byweekday'] = wday_strings
+        else:
+            original_rule = self._original_rule
+
+        partfmt = '{name}={vals}'
+        for name, key in [('BYSETPOS', 'bysetpos'),
+                          ('BYMONTH', 'bymonth'),
+                          ('BYMONTHDAY', 'bymonthday'),
+                          ('BYYEARDAY', 'byyearday'),
+                          ('BYWEEKNO', 'byweekno'),
+                          ('BYDAY', 'byweekday'),
+                          ('BYHOUR', 'byhour'),
+                          ('BYMINUTE', 'byminute'),
+                          ('BYSECOND', 'bysecond'),
+                          ('BYEASTER', 'byeaster')]:
+            value = original_rule.get(key)
+            if value:
+                parts.append(partfmt.format(name=name, vals=(','.join(str(v)
+                                                             for v in value))))
+
+        output.append('RRULE:' + ';'.join(parts))
+        return '\n'.join(output)
+
+    def replace(self, **kwargs):
+        """Return new rrule with same attributes except for those attributes given new
+           values by whichever keyword arguments are specified."""
+        new_kwargs = {"interval": self._interval,
+                      "count": self._count,
+                      "dtstart": self._dtstart,
+                      "freq": self._freq,
+                      "until": self._until,
+                      "wkst": self._wkst,
+                      "cache": False if self._cache is None else True }
+        new_kwargs.update(self._original_rule)
+        new_kwargs.update(kwargs)
+        return rrule(**new_kwargs)
+
+    def _iter(self):
+        year, month, day, hour, minute, second, weekday, yearday, _ = \
+            self._dtstart.timetuple()
+
+        # Some local variables to speed things up a bit
+        freq = self._freq
+        interval = self._interval
+        wkst = self._wkst
+        until = self._until
+        bymonth = self._bymonth
+        byweekno = self._byweekno
+        byyearday = self._byyearday
+        byweekday = self._byweekday
+        byeaster = self._byeaster
+        bymonthday = self._bymonthday
+        bynmonthday = self._bynmonthday
+        bysetpos = self._bysetpos
+        byhour = self._byhour
+        byminute = self._byminute
+        bysecond = self._bysecond
+
+        ii = _iterinfo(self)
+        ii.rebuild(year, month)
+
+        getdayset = {YEARLY: ii.ydayset,
+                     MONTHLY: ii.mdayset,
+                     WEEKLY: ii.wdayset,
+                     DAILY: ii.ddayset,
+                     HOURLY: ii.ddayset,
+                     MINUTELY: ii.ddayset,
+                     SECONDLY: ii.ddayset}[freq]
+
+        if freq < HOURLY:
+            timeset = self._timeset
+        else:
+            gettimeset = {HOURLY: ii.htimeset,
+                          MINUTELY: ii.mtimeset,
+                          SECONDLY: ii.stimeset}[freq]
+            if ((freq >= HOURLY and
+                 self._byhour and hour not in self._byhour) or
+                (freq >= MINUTELY and
+                 self._byminute and minute not in self._byminute) or
+                (freq >= SECONDLY and
+                 self._bysecond and second not in self._bysecond)):
+                timeset = ()
+            else:
+                timeset = gettimeset(hour, minute, second)
+
+        total = 0
+        count = self._count
+        while True:
+            # Get dayset with the right frequency
+            dayset, start, end = getdayset(year, month, day)
+
+            # Do the "hard" work ;-)
+            filtered = False
+            for i in dayset[start:end]:
+                if ((bymonth and ii.mmask[i] not in bymonth) or
+                    (byweekno and not ii.wnomask[i]) or
+                    (byweekday and ii.wdaymask[i] not in byweekday) or
+                    (ii.nwdaymask and not ii.nwdaymask[i]) or
+                    (byeaster and not ii.eastermask[i]) or
+                    ((bymonthday or bynmonthday) and
+                     ii.mdaymask[i] not in bymonthday and
+                     ii.nmdaymask[i] not in bynmonthday) or
+                    (byyearday and
+                     ((i < ii.yearlen and i+1 not in byyearday and
+                       -ii.yearlen+i not in byyearday) or
+                      (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and
+                       -ii.nextyearlen+i-ii.yearlen not in byyearday)))):
+                    dayset[i] = None
+                    filtered = True
+
+            # Output results
+            if bysetpos and timeset:
+                poslist = []
+                for pos in bysetpos:
+                    if pos < 0:
+                        daypos, timepos = divmod(pos, len(timeset))
+                    else:
+                        daypos, timepos = divmod(pos-1, len(timeset))
+                    try:
+                        i = [x for x in dayset[start:end]
+                             if x is not None][daypos]
+                        time = timeset[timepos]
+                    except IndexError:
+                        pass
+                    else:
+                        date = datetime.date.fromordinal(ii.yearordinal+i)
+                        res = datetime.datetime.combine(date, time)
+                        if res not in poslist:
+                            poslist.append(res)
+                poslist.sort()
+                for res in poslist:
+                    if until and res > until:
+                        self._len = total
+                        return
+                    elif res >= self._dtstart:
+                        if count is not None:
+                            count -= 1
+                            if count < 0:
+                                self._len = total
+                                return
+                        total += 1
+                        yield res
+            else:
+                for i in dayset[start:end]:
+                    if i is not None:
+                        date = datetime.date.fromordinal(ii.yearordinal + i)
+                        for time in timeset:
+                            res = datetime.datetime.combine(date, time)
+                            if until and res > until:
+                                self._len = total
+                                return
+                            elif res >= self._dtstart:
+                                if count is not None:
+                                    count -= 1
+                                    if count < 0:
+                                        self._len = total
+                                        return
+
+                                total += 1
+                                yield res
+
+            # Handle frequency and interval
+            fixday = False
+            if freq == YEARLY:
+                year += interval
+                if year > datetime.MAXYEAR:
+                    self._len = total
+                    return
+                ii.rebuild(year, month)
+            elif freq == MONTHLY:
+                month += interval
+                if month > 12:
+                    div, mod = divmod(month, 12)
+                    month = mod
+                    year += div
+                    if month == 0:
+                        month = 12
+                        year -= 1
+                    if year > datetime.MAXYEAR:
+                        self._len = total
+                        return
+                ii.rebuild(year, month)
+            elif freq == WEEKLY:
+                if wkst > weekday:
+                    day += -(weekday+1+(6-wkst))+self._interval*7
+                else:
+                    day += -(weekday-wkst)+self._interval*7
+                weekday = wkst
+                fixday = True
+            elif freq == DAILY:
+                day += interval
+                fixday = True
+            elif freq == HOURLY:
+                if filtered:
+                    # Jump to one iteration before next day
+                    hour += ((23-hour)//interval)*interval
+
+                if byhour:
+                    ndays, hour = self.__mod_distance(value=hour,
+                                                      byxxx=self._byhour,
+                                                      base=24)
+                else:
+                    ndays, hour = divmod(hour+interval, 24)
+
+                if ndays:
+                    day += ndays
+                    fixday = True
+
+                timeset = gettimeset(hour, minute, second)
+            elif freq == MINUTELY:
+                if filtered:
+                    # Jump to one iteration before next day
+                    minute += ((1439-(hour*60+minute))//interval)*interval
+
+                valid = False
+                rep_rate = (24*60)
+                for j in range(rep_rate // gcd(interval, rep_rate)):
+                    if byminute:
+                        nhours, minute = \
+                            self.__mod_distance(value=minute,
+                                                byxxx=self._byminute,
+                                                base=60)
+                    else:
+                        nhours, minute = divmod(minute+interval, 60)
+
+                    div, hour = divmod(hour+nhours, 24)
+                    if div:
+                        day += div
+                        fixday = True
+                        filtered = False
+
+                    if not byhour or hour in byhour:
+                        valid = True
+                        break
+
+                if not valid:
+                    raise ValueError('Invalid combination of interval and ' +
+                                     'byhour resulting in empty rule.')
+
+                timeset = gettimeset(hour, minute, second)
+            elif freq == SECONDLY:
+                if filtered:
+                    # Jump to one iteration before next day
+                    second += (((86399 - (hour * 3600 + minute * 60 + second))
+                                // interval) * interval)
+
+                rep_rate = (24 * 3600)
+                valid = False
+                for j in range(0, rep_rate // gcd(interval, rep_rate)):
+                    if bysecond:
+                        nminutes, second = \
+                            self.__mod_distance(value=second,
+                                                byxxx=self._bysecond,
+                                                base=60)
+                    else:
+                        nminutes, second = divmod(second+interval, 60)
+
+                    div, minute = divmod(minute+nminutes, 60)
+                    if div:
+                        hour += div
+                        div, hour = divmod(hour, 24)
+                        if div:
+                            day += div
+                            fixday = True
+
+                    if ((not byhour or hour in byhour) and
+                            (not byminute or minute in byminute) and
+                            (not bysecond or second in bysecond)):
+                        valid = True
+                        break
+
+                if not valid:
+                    raise ValueError('Invalid combination of interval, ' +
+                                     'byhour and byminute resulting in empty' +
+                                     ' rule.')
+
+                timeset = gettimeset(hour, minute, second)
+
+            if fixday and day > 28:
+                daysinmonth = calendar.monthrange(year, month)[1]
+                if day > daysinmonth:
+                    while day > daysinmonth:
+                        day -= daysinmonth
+                        month += 1
+                        if month == 13:
+                            month = 1
+                            year += 1
+                            if year > datetime.MAXYEAR:
+                                self._len = total
+                                return
+                        daysinmonth = calendar.monthrange(year, month)[1]
+                    ii.rebuild(year, month)
+
+    def __construct_byset(self, start, byxxx, base):
+        """
+        If a `BYXXX` sequence is passed to the constructor at the same level as
+        `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some
+        specifications which cannot be reached given some starting conditions.
+
+        This occurs whenever the interval is not coprime with the base of a
+        given unit and the difference between the starting position and the
+        ending position is not coprime with the greatest common denominator
+        between the interval and the base. For example, with a FREQ of hourly
+        starting at 17:00 and an interval of 4, the only valid values for
+        BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not
+        coprime.
+
+        :param start:
+            Specifies the starting position.
+        :param byxxx:
+            An iterable containing the list of allowed values.
+        :param base:
+            The largest allowable value for the specified frequency (e.g.
+            24 hours, 60 minutes).
+
+        This does not preserve the type of the iterable, returning a set, since
+        the values should be unique and the order is irrelevant, this will
+        speed up later lookups.
+
+        In the event of an empty set, raises a :exception:`ValueError`, as this
+        results in an empty rrule.
+        """
+
+        cset = set()
+
+        # Support a single byxxx value.
+        if isinstance(byxxx, integer_types):
+            byxxx = (byxxx, )
+
+        for num in byxxx:
+            i_gcd = gcd(self._interval, base)
+            # Use divmod rather than % because we need to wrap negative nums.
+            if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:
+                cset.add(num)
+
+        if len(cset) == 0:
+            raise ValueError("Invalid rrule byxxx generates an empty set.")
+
+        return cset
+
+    def __mod_distance(self, value, byxxx, base):
+        """
+        Calculates the next value in a sequence where the `FREQ` parameter is
+        specified along with a `BYXXX` parameter at the same "level"
+        (e.g. `HOURLY` specified with `BYHOUR`).
+
+        :param value:
+            The old value of the component.
+        :param byxxx:
+            The `BYXXX` set, which should have been generated by
+            `rrule._construct_byset`, or something else which checks that a
+            valid rule is present.
+        :param base:
+            The largest allowable value for the specified frequency (e.g.
+            24 hours, 60 minutes).
+
+        If a valid value is not found after `base` iterations (the maximum
+        number before the sequence would start to repeat), this raises a
+        :exception:`ValueError`, as no valid values were found.
+
+        This returns a tuple of `divmod(n*interval, base)`, where `n` is the
+        smallest number of `interval` repetitions until the next specified
+        value in `byxxx` is found.
+        """
+        accumulator = 0
+        for ii in range(1, base + 1):
+            # Using divmod() over % to account for negative intervals
+            div, value = divmod(value + self._interval, base)
+            accumulator += div
+            if value in byxxx:
+                return (accumulator, value)
+
+
+class _iterinfo(object):
+    __slots__ = ["rrule", "lastyear", "lastmonth",
+                 "yearlen", "nextyearlen", "yearordinal", "yearweekday",
+                 "mmask", "mrange", "mdaymask", "nmdaymask",
+                 "wdaymask", "wnomask", "nwdaymask", "eastermask"]
+
+    def __init__(self, rrule):
+        for attr in self.__slots__:
+            setattr(self, attr, None)
+        self.rrule = rrule
+
+    def rebuild(self, year, month):
+        # Every mask is 7 days longer to handle cross-year weekly periods.
+        rr = self.rrule
+        if year != self.lastyear:
+            self.yearlen = 365 + calendar.isleap(year)
+            self.nextyearlen = 365 + calendar.isleap(year + 1)
+            firstyday = datetime.date(year, 1, 1)
+            self.yearordinal = firstyday.toordinal()
+            self.yearweekday = firstyday.weekday()
+
+            wday = datetime.date(year, 1, 1).weekday()
+            if self.yearlen == 365:
+                self.mmask = M365MASK
+                self.mdaymask = MDAY365MASK
+                self.nmdaymask = NMDAY365MASK
+                self.wdaymask = WDAYMASK[wday:]
+                self.mrange = M365RANGE
+            else:
+                self.mmask = M366MASK
+                self.mdaymask = MDAY366MASK
+                self.nmdaymask = NMDAY366MASK
+                self.wdaymask = WDAYMASK[wday:]
+                self.mrange = M366RANGE
+
+            if not rr._byweekno:
+                self.wnomask = None
+            else:
+                self.wnomask = [0]*(self.yearlen+7)
+                # no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
+                no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7
+                if no1wkst >= 4:
+                    no1wkst = 0
+                    # Number of days in the year, plus the days we got
+                    # from last year.
+                    wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7
+                else:
+                    # Number of days in the year, minus the days we
+                    # left in last year.
+                    wyearlen = self.yearlen-no1wkst
+                div, mod = divmod(wyearlen, 7)
+                numweeks = div+mod//4
+                for n in rr._byweekno:
+                    if n < 0:
+                        n += numweeks+1
+                    if not (0 < n <= numweeks):
+                        continue
+                    if n > 1:
+                        i = no1wkst+(n-1)*7
+                        if no1wkst != firstwkst:
+                            i -= 7-firstwkst
+                    else:
+                        i = no1wkst
+                    for j in range(7):
+                        self.wnomask[i] = 1
+                        i += 1
+                        if self.wdaymask[i] == rr._wkst:
+                            break
+                if 1 in rr._byweekno:
+                    # Check week number 1 of next year as well
+                    # TODO: Check -numweeks for next year.
+                    i = no1wkst+numweeks*7
+                    if no1wkst != firstwkst:
+                        i -= 7-firstwkst
+                    if i < self.yearlen:
+                        # If week starts in next year, we
+                        # don't care about it.
+                        for j in range(7):
+                            self.wnomask[i] = 1
+                            i += 1
+                            if self.wdaymask[i] == rr._wkst:
+                                break
+                if no1wkst:
+                    # Check last week number of last year as
+                    # well. If no1wkst is 0, either the year
+                    # started on week start, or week number 1
+                    # got days from last year, so there are no
+                    # days from last year's last week number in
+                    # this year.
+                    if -1 not in rr._byweekno:
+                        lyearweekday = datetime.date(year-1, 1, 1).weekday()
+                        lno1wkst = (7-lyearweekday+rr._wkst) % 7
+                        lyearlen = 365+calendar.isleap(year-1)
+                        if lno1wkst >= 4:
+                            lno1wkst = 0
+                            lnumweeks = 52+(lyearlen +
+                                            (lyearweekday-rr._wkst) % 7) % 7//4
+                        else:
+                            lnumweeks = 52+(self.yearlen-no1wkst) % 7//4
+                    else:
+                        lnumweeks = -1
+                    if lnumweeks in rr._byweekno:
+                        for i in range(no1wkst):
+                            self.wnomask[i] = 1
+
+        if (rr._bynweekday and (month != self.lastmonth or
+                                year != self.lastyear)):
+            ranges = []
+            if rr._freq == YEARLY:
+                if rr._bymonth:
+                    for month in rr._bymonth:
+                        ranges.append(self.mrange[month-1:month+1])
+                else:
+                    ranges = [(0, self.yearlen)]
+            elif rr._freq == MONTHLY:
+                ranges = [self.mrange[month-1:month+1]]
+            if ranges:
+                # Weekly frequency won't get here, so we may not
+                # care about cross-year weekly periods.
+                self.nwdaymask = [0]*self.yearlen
+                for first, last in ranges:
+                    last -= 1
+                    for wday, n in rr._bynweekday:
+                        if n < 0:
+                            i = last+(n+1)*7
+                            i -= (self.wdaymask[i]-wday) % 7
+                        else:
+                            i = first+(n-1)*7
+                            i += (7-self.wdaymask[i]+wday) % 7
+                        if first <= i <= last:
+                            self.nwdaymask[i] = 1
+
+        if rr._byeaster:
+            self.eastermask = [0]*(self.yearlen+7)
+            eyday = easter.easter(year).toordinal()-self.yearordinal
+            for offset in rr._byeaster:
+                self.eastermask[eyday+offset] = 1
+
+        self.lastyear = year
+        self.lastmonth = month
+
+    def ydayset(self, year, month, day):
+        return list(range(self.yearlen)), 0, self.yearlen
+
+    def mdayset(self, year, month, day):
+        dset = [None]*self.yearlen
+        start, end = self.mrange[month-1:month+1]
+        for i in range(start, end):
+            dset[i] = i
+        return dset, start, end
+
+    def wdayset(self, year, month, day):
+        # We need to handle cross-year weeks here.
+        dset = [None]*(self.yearlen+7)
+        i = datetime.date(year, month, day).toordinal()-self.yearordinal
+        start = i
+        for j in range(7):
+            dset[i] = i
+            i += 1
+            # if (not (0 <= i < self.yearlen) or
+            #    self.wdaymask[i] == self.rrule._wkst):
+            # This will cross the year boundary, if necessary.
+            if self.wdaymask[i] == self.rrule._wkst:
+                break
+        return dset, start, i
+
+    def ddayset(self, year, month, day):
+        dset = [None] * self.yearlen
+        i = datetime.date(year, month, day).toordinal() - self.yearordinal
+        dset[i] = i
+        return dset, i, i + 1
+
+    def htimeset(self, hour, minute, second):
+        tset = []
+        rr = self.rrule
+        for minute in rr._byminute:
+            for second in rr._bysecond:
+                tset.append(datetime.time(hour, minute, second,
+                                          tzinfo=rr._tzinfo))
+        tset.sort()
+        return tset
+
+    def mtimeset(self, hour, minute, second):
+        tset = []
+        rr = self.rrule
+        for second in rr._bysecond:
+            tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
+        tset.sort()
+        return tset
+
+    def stimeset(self, hour, minute, second):
+        return (datetime.time(hour, minute, second,
+                tzinfo=self.rrule._tzinfo),)
+
+
+class rruleset(rrulebase):
+    """ The rruleset type allows more complex recurrence setups, mixing
+    multiple rules, dates, exclusion rules, and exclusion dates. The type
+    constructor takes the following keyword arguments:
+
+    :param cache: If True, caching of results will be enabled, improving
+                  performance of multiple queries considerably. """
+
+    class _genitem(object):
+        def __init__(self, genlist, gen):
+            try:
+                self.dt = advance_iterator(gen)
+                genlist.append(self)
+            except StopIteration:
+                pass
+            self.genlist = genlist
+            self.gen = gen
+
+        def __next__(self):
+            try:
+                self.dt = advance_iterator(self.gen)
+            except StopIteration:
+                if self.genlist[0] is self:
+                    heapq.heappop(self.genlist)
+                else:
+                    self.genlist.remove(self)
+                    heapq.heapify(self.genlist)
+
+        next = __next__
+
+        def __lt__(self, other):
+            return self.dt < other.dt
+
+        def __gt__(self, other):
+            return self.dt > other.dt
+
+        def __eq__(self, other):
+            return self.dt == other.dt
+
+        def __ne__(self, other):
+            return self.dt != other.dt
+
+    def __init__(self, cache=False):
+        super(rruleset, self).__init__(cache)
+        self._rrule = []
+        self._rdate = []
+        self._exrule = []
+        self._exdate = []
+
+    @_invalidates_cache
+    def rrule(self, rrule):
+        """ Include the given :py:class:`rrule` instance in the recurrence set
+            generation. """
+        self._rrule.append(rrule)
+
+    @_invalidates_cache
+    def rdate(self, rdate):
+        """ Include the given :py:class:`datetime` instance in the recurrence
+            set generation. """
+        self._rdate.append(rdate)
+
+    @_invalidates_cache
+    def exrule(self, exrule):
+        """ Include the given rrule instance in the recurrence set exclusion
+            list. Dates which are part of the given recurrence rules will not
+            be generated, even if some inclusive rrule or rdate matches them.
+        """
+        self._exrule.append(exrule)
+
+    @_invalidates_cache
+    def exdate(self, exdate):
+        """ Include the given datetime instance in the recurrence set
+            exclusion list. Dates included that way will not be generated,
+            even if some inclusive rrule or rdate matches them. """
+        self._exdate.append(exdate)
+
+    def _iter(self):
+        rlist = []
+        self._rdate.sort()
+        self._genitem(rlist, iter(self._rdate))
+        for gen in [iter(x) for x in self._rrule]:
+            self._genitem(rlist, gen)
+        exlist = []
+        self._exdate.sort()
+        self._genitem(exlist, iter(self._exdate))
+        for gen in [iter(x) for x in self._exrule]:
+            self._genitem(exlist, gen)
+        lastdt = None
+        total = 0
+        heapq.heapify(rlist)
+        heapq.heapify(exlist)
+        while rlist:
+            ritem = rlist[0]
+            if not lastdt or lastdt != ritem.dt:
+                while exlist and exlist[0] < ritem:
+                    exitem = exlist[0]
+                    advance_iterator(exitem)
+                    if exlist and exlist[0] is exitem:
+                        heapq.heapreplace(exlist, exitem)
+                if not exlist or ritem != exlist[0]:
+                    total += 1
+                    yield ritem.dt
+                lastdt = ritem.dt
+            advance_iterator(ritem)
+            if rlist and rlist[0] is ritem:
+                heapq.heapreplace(rlist, ritem)
+        self._len = total
+
+
+
+
+class _rrulestr(object):
+    """ Parses a string representation of a recurrence rule or set of
+    recurrence rules.
+
+    :param s:
+        Required, a string defining one or more recurrence rules.
+
+    :param dtstart:
+        If given, used as the default recurrence start if not specified in the
+        rule string.
+
+    :param cache:
+        If set ``True`` caching of results will be enabled, improving
+        performance of multiple queries considerably.
+
+    :param unfold:
+        If set ``True`` indicates that a rule string is split over more
+        than one line and should be joined before processing.
+
+    :param forceset:
+        If set ``True`` forces a :class:`dateutil.rrule.rruleset` to
+        be returned.
+
+    :param compatible:
+        If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``.
+
+    :param ignoretz:
+        If set ``True``, time zones in parsed strings are ignored and a naive
+        :class:`datetime.datetime` object is returned.
+
+    :param tzids:
+        If given, a callable or mapping used to retrieve a
+        :class:`datetime.tzinfo` from a string representation.
+        Defaults to :func:`dateutil.tz.gettz`.
+
+    :param tzinfos:
+        Additional time zone names / aliases which may be present in a string
+        representation.  See :func:`dateutil.parser.parse` for more
+        information.
+
+    :return:
+        Returns a :class:`dateutil.rrule.rruleset` or
+        :class:`dateutil.rrule.rrule`
+    """
+
+    _freq_map = {"YEARLY": YEARLY,
+                 "MONTHLY": MONTHLY,
+                 "WEEKLY": WEEKLY,
+                 "DAILY": DAILY,
+                 "HOURLY": HOURLY,
+                 "MINUTELY": MINUTELY,
+                 "SECONDLY": SECONDLY}
+
+    _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3,
+                    "FR": 4, "SA": 5, "SU": 6}
+
+    def _handle_int(self, rrkwargs, name, value, **kwargs):
+        rrkwargs[name.lower()] = int(value)
+
+    def _handle_int_list(self, rrkwargs, name, value, **kwargs):
+        rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
+
+    _handle_INTERVAL = _handle_int
+    _handle_COUNT = _handle_int
+    _handle_BYSETPOS = _handle_int_list
+    _handle_BYMONTH = _handle_int_list
+    _handle_BYMONTHDAY = _handle_int_list
+    _handle_BYYEARDAY = _handle_int_list
+    _handle_BYEASTER = _handle_int_list
+    _handle_BYWEEKNO = _handle_int_list
+    _handle_BYHOUR = _handle_int_list
+    _handle_BYMINUTE = _handle_int_list
+    _handle_BYSECOND = _handle_int_list
+
+    def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
+        rrkwargs["freq"] = self._freq_map[value]
+
+    def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
+        global parser
+        if not parser:
+            from dateutil import parser
+        try:
+            rrkwargs["until"] = parser.parse(value,
+                                             ignoretz=kwargs.get("ignoretz"),
+                                             tzinfos=kwargs.get("tzinfos"))
+        except ValueError:
+            raise ValueError("invalid until date")
+
+    def _handle_WKST(self, rrkwargs, name, value, **kwargs):
+        rrkwargs["wkst"] = self._weekday_map[value]
+
+    def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs):
+        """
+        Two ways to specify this: +1MO or MO(+1)
+        """
+        l = []
+        for wday in value.split(','):
+            if '(' in wday:
+                # If it's of the form TH(+1), etc.
+                splt = wday.split('(')
+                w = splt[0]
+                n = int(splt[1][:-1])
+            elif len(wday):
+                # If it's of the form +1MO
+                for i in range(len(wday)):
+                    if wday[i] not in '+-0123456789':
+                        break
+                n = wday[:i] or None
+                w = wday[i:]
+                if n:
+                    n = int(n)
+            else:
+                raise ValueError("Invalid (empty) BYDAY specification.")
+
+            l.append(weekdays[self._weekday_map[w]](n))
+        rrkwargs["byweekday"] = l
+
+    _handle_BYDAY = _handle_BYWEEKDAY
+
+    def _parse_rfc_rrule(self, line,
+                         dtstart=None,
+                         cache=False,
+                         ignoretz=False,
+                         tzinfos=None):
+        if line.find(':') != -1:
+            name, value = line.split(':')
+            if name != "RRULE":
+                raise ValueError("unknown parameter name")
+        else:
+            value = line
+        rrkwargs = {}
+        for pair in value.split(';'):
+            name, value = pair.split('=')
+            name = name.upper()
+            value = value.upper()
+            try:
+                getattr(self, "_handle_"+name)(rrkwargs, name, value,
+                                               ignoretz=ignoretz,
+                                               tzinfos=tzinfos)
+            except AttributeError:
+                raise ValueError("unknown parameter '%s'" % name)
+            except (KeyError, ValueError):
+                raise ValueError("invalid '%s': %s" % (name, value))
+        return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
+
+    def _parse_date_value(self, date_value, parms, rule_tzids,
+                          ignoretz, tzids, tzinfos):
+        global parser
+        if not parser:
+            from dateutil import parser
+
+        datevals = []
+        value_found = False
+        TZID = None
+
+        for parm in parms:
+            if parm.startswith("TZID="):
+                try:
+                    tzkey = rule_tzids[parm.split('TZID=')[-1]]
+                except KeyError:
+                    continue
+                if tzids is None:
+                    from . import tz
+                    tzlookup = tz.gettz
+                elif callable(tzids):
+                    tzlookup = tzids
+                else:
+                    tzlookup = getattr(tzids, 'get', None)
+                    if tzlookup is None:
+                        msg = ('tzids must be a callable, mapping, or None, '
+                               'not %s' % tzids)
+                        raise ValueError(msg)
+
+                TZID = tzlookup(tzkey)
+                continue
+
+            # RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found
+            # only once.
+            if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}:
+                raise ValueError("unsupported parm: " + parm)
+            else:
+                if value_found:
+                    msg = ("Duplicate value parameter found in: " + parm)
+                    raise ValueError(msg)
+                value_found = True
+
+        for datestr in date_value.split(','):
+            date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos)
+            if TZID is not None:
+                if date.tzinfo is None:
+                    date = date.replace(tzinfo=TZID)
+                else:
+                    raise ValueError('DTSTART/EXDATE specifies multiple timezone')
+            datevals.append(date)
+
+        return datevals
+
+    def _parse_rfc(self, s,
+                   dtstart=None,
+                   cache=False,
+                   unfold=False,
+                   forceset=False,
+                   compatible=False,
+                   ignoretz=False,
+                   tzids=None,
+                   tzinfos=None):
+        global parser
+        if compatible:
+            forceset = True
+            unfold = True
+
+        TZID_NAMES = dict(map(
+            lambda x: (x.upper(), x),
+            re.findall('TZID=(?P<name>[^:]+):', s)
+        ))
+        s = s.upper()
+        if not s.strip():
+            raise ValueError("empty string")
+        if unfold:
+            lines = s.splitlines()
+            i = 0
+            while i < len(lines):
+                line = lines[i].rstrip()
+                if not line:
+                    del lines[i]
+                elif i > 0 and line[0] == " ":
+                    lines[i-1] += line[1:]
+                    del lines[i]
+                else:
+                    i += 1
+        else:
+            lines = s.split()
+        if (not forceset and len(lines) == 1 and (s.find(':') == -1 or
+                                                  s.startswith('RRULE:'))):
+            return self._parse_rfc_rrule(lines[0], cache=cache,
+                                         dtstart=dtstart, ignoretz=ignoretz,
+                                         tzinfos=tzinfos)
+        else:
+            rrulevals = []
+            rdatevals = []
+            exrulevals = []
+            exdatevals = []
+            for line in lines:
+                if not line:
+                    continue
+                if line.find(':') == -1:
+                    name = "RRULE"
+                    value = line
+                else:
+                    name, value = line.split(':', 1)
+                parms = name.split(';')
+                if not parms:
+                    raise ValueError("empty property name")
+                name = parms[0]
+                parms = parms[1:]
+                if name == "RRULE":
+                    for parm in parms:
+                        raise ValueError("unsupported RRULE parm: "+parm)
+                    rrulevals.append(value)
+                elif name == "RDATE":
+                    for parm in parms:
+                        if parm != "VALUE=DATE-TIME":
+                            raise ValueError("unsupported RDATE parm: "+parm)
+                    rdatevals.append(value)
+                elif name == "EXRULE":
+                    for parm in parms:
+                        raise ValueError("unsupported EXRULE parm: "+parm)
+                    exrulevals.append(value)
+                elif name == "EXDATE":
+                    exdatevals.extend(
+                        self._parse_date_value(value, parms,
+                                               TZID_NAMES, ignoretz,
+                                               tzids, tzinfos)
+                    )
+                elif name == "DTSTART":
+                    dtvals = self._parse_date_value(value, parms, TZID_NAMES,
+                                                    ignoretz, tzids, tzinfos)
+                    if len(dtvals) != 1:
+                        raise ValueError("Multiple DTSTART values specified:" +
+                                         value)
+                    dtstart = dtvals[0]
+                else:
+                    raise ValueError("unsupported property: "+name)
+            if (forceset or len(rrulevals) > 1 or rdatevals
+                    or exrulevals or exdatevals):
+                if not parser and (rdatevals or exdatevals):
+                    from dateutil import parser
+                rset = rruleset(cache=cache)
+                for value in rrulevals:
+                    rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
+                                                     ignoretz=ignoretz,
+                                                     tzinfos=tzinfos))
+                for value in rdatevals:
+                    for datestr in value.split(','):
+                        rset.rdate(parser.parse(datestr,
+                                                ignoretz=ignoretz,
+                                                tzinfos=tzinfos))
+                for value in exrulevals:
+                    rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
+                                                      ignoretz=ignoretz,
+                                                      tzinfos=tzinfos))
+                for value in exdatevals:
+                    rset.exdate(value)
+                if compatible and dtstart:
+                    rset.rdate(dtstart)
+                return rset
+            else:
+                return self._parse_rfc_rrule(rrulevals[0],
+                                             dtstart=dtstart,
+                                             cache=cache,
+                                             ignoretz=ignoretz,
+                                             tzinfos=tzinfos)
+
+    def __call__(self, s, **kwargs):
+        return self._parse_rfc(s, **kwargs)
+
+
+rrulestr = _rrulestr()
+
+# vim:ts=4:sw=4:et

+ 12 - 0
venv/lib/python3.8/site-packages/dateutil/tz/__init__.py

@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+from .tz import *
+from .tz import __doc__
+
+__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
+           "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
+           "enfold", "datetime_ambiguous", "datetime_exists",
+           "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
+
+
+class DeprecatedTzFormatWarning(Warning):
+    """Warning raised when time zones are parsed from deprecated formats."""

+ 419 - 0
venv/lib/python3.8/site-packages/dateutil/tz/_common.py

@@ -0,0 +1,419 @@
+from six import PY2
+
+from functools import wraps
+
+from datetime import datetime, timedelta, tzinfo
+
+
+ZERO = timedelta(0)
+
+__all__ = ['tzname_in_python2', 'enfold']
+
+
+def tzname_in_python2(namefunc):
+    """Change unicode output into bytestrings in Python 2
+
+    tzname() API changed in Python 3. It used to return bytes, but was changed
+    to unicode strings
+    """
+    if PY2:
+        @wraps(namefunc)
+        def adjust_encoding(*args, **kwargs):
+            name = namefunc(*args, **kwargs)
+            if name is not None:
+                name = name.encode()
+
+            return name
+
+        return adjust_encoding
+    else:
+        return namefunc
+
+
+# The following is adapted from Alexander Belopolsky's tz library
+# https://github.com/abalkin/tz
+if hasattr(datetime, 'fold'):
+    # This is the pre-python 3.6 fold situation
+    def enfold(dt, fold=1):
+        """
+        Provides a unified interface for assigning the ``fold`` attribute to
+        datetimes both before and after the implementation of PEP-495.
+
+        :param fold:
+            The value for the ``fold`` attribute in the returned datetime. This
+            should be either 0 or 1.
+
+        :return:
+            Returns an object for which ``getattr(dt, 'fold', 0)`` returns
+            ``fold`` for all versions of Python. In versions prior to
+            Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
+            subclass of :py:class:`datetime.datetime` with the ``fold``
+            attribute added, if ``fold`` is 1.
+
+        .. versionadded:: 2.6.0
+        """
+        return dt.replace(fold=fold)
+
+else:
+    class _DatetimeWithFold(datetime):
+        """
+        This is a class designed to provide a PEP 495-compliant interface for
+        Python versions before 3.6. It is used only for dates in a fold, so
+        the ``fold`` attribute is fixed at ``1``.
+
+        .. versionadded:: 2.6.0
+        """
+        __slots__ = ()
+
+        def replace(self, *args, **kwargs):
+            """
+            Return a datetime with the same attributes, except for those
+            attributes given new values by whichever keyword arguments are
+            specified. Note that tzinfo=None can be specified to create a naive
+            datetime from an aware datetime with no conversion of date and time
+            data.
+
+            This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
+            return a ``datetime.datetime`` even if ``fold`` is unchanged.
+            """
+            argnames = (
+                'year', 'month', 'day', 'hour', 'minute', 'second',
+                'microsecond', 'tzinfo'
+            )
+
+            for arg, argname in zip(args, argnames):
+                if argname in kwargs:
+                    raise TypeError('Duplicate argument: {}'.format(argname))
+
+                kwargs[argname] = arg
+
+            for argname in argnames:
+                if argname not in kwargs:
+                    kwargs[argname] = getattr(self, argname)
+
+            dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
+
+            return dt_class(**kwargs)
+
+        @property
+        def fold(self):
+            return 1
+
+    def enfold(dt, fold=1):
+        """
+        Provides a unified interface for assigning the ``fold`` attribute to
+        datetimes both before and after the implementation of PEP-495.
+
+        :param fold:
+            The value for the ``fold`` attribute in the returned datetime. This
+            should be either 0 or 1.
+
+        :return:
+            Returns an object for which ``getattr(dt, 'fold', 0)`` returns
+            ``fold`` for all versions of Python. In versions prior to
+            Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
+            subclass of :py:class:`datetime.datetime` with the ``fold``
+            attribute added, if ``fold`` is 1.
+
+        .. versionadded:: 2.6.0
+        """
+        if getattr(dt, 'fold', 0) == fold:
+            return dt
+
+        args = dt.timetuple()[:6]
+        args += (dt.microsecond, dt.tzinfo)
+
+        if fold:
+            return _DatetimeWithFold(*args)
+        else:
+            return datetime(*args)
+
+
+def _validate_fromutc_inputs(f):
+    """
+    The CPython version of ``fromutc`` checks that the input is a ``datetime``
+    object and that ``self`` is attached as its ``tzinfo``.
+    """
+    @wraps(f)
+    def fromutc(self, dt):
+        if not isinstance(dt, datetime):
+            raise TypeError("fromutc() requires a datetime argument")
+        if dt.tzinfo is not self:
+            raise ValueError("dt.tzinfo is not self")
+
+        return f(self, dt)
+
+    return fromutc
+
+
+class _tzinfo(tzinfo):
+    """
+    Base class for all ``dateutil`` ``tzinfo`` objects.
+    """
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+
+        dt = dt.replace(tzinfo=self)
+
+        wall_0 = enfold(dt, fold=0)
+        wall_1 = enfold(dt, fold=1)
+
+        same_offset = wall_0.utcoffset() == wall_1.utcoffset()
+        same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
+
+        return same_dt and not same_offset
+
+    def _fold_status(self, dt_utc, dt_wall):
+        """
+        Determine the fold status of a "wall" datetime, given a representation
+        of the same datetime as a (naive) UTC datetime. This is calculated based
+        on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
+        datetimes, and that this offset is the actual number of hours separating
+        ``dt_utc`` and ``dt_wall``.
+
+        :param dt_utc:
+            Representation of the datetime as UTC
+
+        :param dt_wall:
+            Representation of the datetime as "wall time". This parameter must
+            either have a `fold` attribute or have a fold-naive
+            :class:`datetime.tzinfo` attached, otherwise the calculation may
+            fail.
+        """
+        if self.is_ambiguous(dt_wall):
+            delta_wall = dt_wall - dt_utc
+            _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
+        else:
+            _fold = 0
+
+        return _fold
+
+    def _fold(self, dt):
+        return getattr(dt, 'fold', 0)
+
+    def _fromutc(self, dt):
+        """
+        Given a timezone-aware datetime in a given timezone, calculates a
+        timezone-aware datetime in a new timezone.
+
+        Since this is the one time that we *know* we have an unambiguous
+        datetime object, we take this opportunity to determine whether the
+        datetime is ambiguous and in a "fold" state (e.g. if it's the first
+        occurrence, chronologically, of the ambiguous datetime).
+
+        :param dt:
+            A timezone-aware :class:`datetime.datetime` object.
+        """
+
+        # Re-implement the algorithm from Python's datetime.py
+        dtoff = dt.utcoffset()
+        if dtoff is None:
+            raise ValueError("fromutc() requires a non-None utcoffset() "
+                             "result")
+
+        # The original datetime.py code assumes that `dst()` defaults to
+        # zero during ambiguous times. PEP 495 inverts this presumption, so
+        # for pre-PEP 495 versions of python, we need to tweak the algorithm.
+        dtdst = dt.dst()
+        if dtdst is None:
+            raise ValueError("fromutc() requires a non-None dst() result")
+        delta = dtoff - dtdst
+
+        dt += delta
+        # Set fold=1 so we can default to being in the fold for
+        # ambiguous dates.
+        dtdst = enfold(dt, fold=1).dst()
+        if dtdst is None:
+            raise ValueError("fromutc(): dt.dst gave inconsistent "
+                             "results; cannot convert")
+        return dt + dtdst
+
+    @_validate_fromutc_inputs
+    def fromutc(self, dt):
+        """
+        Given a timezone-aware datetime in a given timezone, calculates a
+        timezone-aware datetime in a new timezone.
+
+        Since this is the one time that we *know* we have an unambiguous
+        datetime object, we take this opportunity to determine whether the
+        datetime is ambiguous and in a "fold" state (e.g. if it's the first
+        occurrence, chronologically, of the ambiguous datetime).
+
+        :param dt:
+            A timezone-aware :class:`datetime.datetime` object.
+        """
+        dt_wall = self._fromutc(dt)
+
+        # Calculate the fold status given the two datetimes.
+        _fold = self._fold_status(dt, dt_wall)
+
+        # Set the default fold value for ambiguous dates
+        return enfold(dt_wall, fold=_fold)
+
+
+class tzrangebase(_tzinfo):
+    """
+    This is an abstract base class for time zones represented by an annual
+    transition into and out of DST. Child classes should implement the following
+    methods:
+
+        * ``__init__(self, *args, **kwargs)``
+        * ``transitions(self, year)`` - this is expected to return a tuple of
+          datetimes representing the DST on and off transitions in standard
+          time.
+
+    A fully initialized ``tzrangebase`` subclass should also provide the
+    following attributes:
+        * ``hasdst``: Boolean whether or not the zone uses DST.
+        * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
+          representing the respective UTC offsets.
+        * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
+          abbreviations in DST and STD, respectively.
+        * ``_hasdst``: Whether or not the zone has DST.
+
+    .. versionadded:: 2.6.0
+    """
+    def __init__(self):
+        raise NotImplementedError('tzrangebase is an abstract base class')
+
+    def utcoffset(self, dt):
+        isdst = self._isdst(dt)
+
+        if isdst is None:
+            return None
+        elif isdst:
+            return self._dst_offset
+        else:
+            return self._std_offset
+
+    def dst(self, dt):
+        isdst = self._isdst(dt)
+
+        if isdst is None:
+            return None
+        elif isdst:
+            return self._dst_base_offset
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        if self._isdst(dt):
+            return self._dst_abbr
+        else:
+            return self._std_abbr
+
+    def fromutc(self, dt):
+        """ Given a datetime in UTC, return local time """
+        if not isinstance(dt, datetime):
+            raise TypeError("fromutc() requires a datetime argument")
+
+        if dt.tzinfo is not self:
+            raise ValueError("dt.tzinfo is not self")
+
+        # Get transitions - if there are none, fixed offset
+        transitions = self.transitions(dt.year)
+        if transitions is None:
+            return dt + self.utcoffset(dt)
+
+        # Get the transition times in UTC
+        dston, dstoff = transitions
+
+        dston -= self._std_offset
+        dstoff -= self._std_offset
+
+        utc_transitions = (dston, dstoff)
+        dt_utc = dt.replace(tzinfo=None)
+
+        isdst = self._naive_isdst(dt_utc, utc_transitions)
+
+        if isdst:
+            dt_wall = dt + self._dst_offset
+        else:
+            dt_wall = dt + self._std_offset
+
+        _fold = int(not isdst and self.is_ambiguous(dt_wall))
+
+        return enfold(dt_wall, fold=_fold)
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        if not self.hasdst:
+            return False
+
+        start, end = self.transitions(dt.year)
+
+        dt = dt.replace(tzinfo=None)
+        return (end <= dt < end + self._dst_base_offset)
+
+    def _isdst(self, dt):
+        if not self.hasdst:
+            return False
+        elif dt is None:
+            return None
+
+        transitions = self.transitions(dt.year)
+
+        if transitions is None:
+            return False
+
+        dt = dt.replace(tzinfo=None)
+
+        isdst = self._naive_isdst(dt, transitions)
+
+        # Handle ambiguous dates
+        if not isdst and self.is_ambiguous(dt):
+            return not self._fold(dt)
+        else:
+            return isdst
+
+    def _naive_isdst(self, dt, transitions):
+        dston, dstoff = transitions
+
+        dt = dt.replace(tzinfo=None)
+
+        if dston < dstoff:
+            isdst = dston <= dt < dstoff
+        else:
+            isdst = not dstoff <= dt < dston
+
+        return isdst
+
+    @property
+    def _dst_base_offset(self):
+        return self._dst_offset - self._std_offset
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s(...)" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__

+ 80 - 0
venv/lib/python3.8/site-packages/dateutil/tz/_factories.py

@@ -0,0 +1,80 @@
+from datetime import timedelta
+import weakref
+from collections import OrderedDict
+
+from six.moves import _thread
+
+
+class _TzSingleton(type):
+    def __init__(cls, *args, **kwargs):
+        cls.__instance = None
+        super(_TzSingleton, cls).__init__(*args, **kwargs)
+
+    def __call__(cls):
+        if cls.__instance is None:
+            cls.__instance = super(_TzSingleton, cls).__call__()
+        return cls.__instance
+
+
+class _TzFactory(type):
+    def instance(cls, *args, **kwargs):
+        """Alternate constructor that returns a fresh instance"""
+        return type.__call__(cls, *args, **kwargs)
+
+
+class _TzOffsetFactory(_TzFactory):
+    def __init__(cls, *args, **kwargs):
+        cls.__instances = weakref.WeakValueDictionary()
+        cls.__strong_cache = OrderedDict()
+        cls.__strong_cache_size = 8
+
+        cls._cache_lock = _thread.allocate_lock()
+
+    def __call__(cls, name, offset):
+        if isinstance(offset, timedelta):
+            key = (name, offset.total_seconds())
+        else:
+            key = (name, offset)
+
+        instance = cls.__instances.get(key, None)
+        if instance is None:
+            instance = cls.__instances.setdefault(key,
+                                                  cls.instance(name, offset))
+
+        # This lock may not be necessary in Python 3. See GH issue #901
+        with cls._cache_lock:
+            cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
+
+            # Remove an item if the strong cache is overpopulated
+            if len(cls.__strong_cache) > cls.__strong_cache_size:
+                cls.__strong_cache.popitem(last=False)
+
+        return instance
+
+
+class _TzStrFactory(_TzFactory):
+    def __init__(cls, *args, **kwargs):
+        cls.__instances = weakref.WeakValueDictionary()
+        cls.__strong_cache = OrderedDict()
+        cls.__strong_cache_size = 8
+
+        cls.__cache_lock = _thread.allocate_lock()
+
+    def __call__(cls, s, posix_offset=False):
+        key = (s, posix_offset)
+        instance = cls.__instances.get(key, None)
+
+        if instance is None:
+            instance = cls.__instances.setdefault(key,
+                cls.instance(s, posix_offset))
+
+        # This lock may not be necessary in Python 3. See GH issue #901
+        with cls.__cache_lock:
+            cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
+
+            # Remove an item if the strong cache is overpopulated
+            if len(cls.__strong_cache) > cls.__strong_cache_size:
+                cls.__strong_cache.popitem(last=False)
+
+        return instance
+

+ 1849 - 0
venv/lib/python3.8/site-packages/dateutil/tz/tz.py

@@ -0,0 +1,1849 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers timezone implementations subclassing the abstract
+:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
+files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
+etc), TZ environment string (in all known formats), given ranges (with help
+from relative deltas), local machine timezone, fixed offset timezone, and UTC
+timezone.
+"""
+import datetime
+import struct
+import time
+import sys
+import os
+import bisect
+import weakref
+from collections import OrderedDict
+
+import six
+from six import string_types
+from six.moves import _thread
+from ._common import tzname_in_python2, _tzinfo
+from ._common import tzrangebase, enfold
+from ._common import _validate_fromutc_inputs
+
+from ._factories import _TzSingleton, _TzOffsetFactory
+from ._factories import _TzStrFactory
+try:
+    from .win import tzwin, tzwinlocal
+except ImportError:
+    tzwin = tzwinlocal = None
+
+# For warning about rounding tzinfo
+from warnings import warn
+
+ZERO = datetime.timedelta(0)
+EPOCH = datetime.datetime.utcfromtimestamp(0)
+EPOCHORDINAL = EPOCH.toordinal()
+
+
+@six.add_metaclass(_TzSingleton)
+class tzutc(datetime.tzinfo):
+    """
+    This is a tzinfo object that represents the UTC time zone.
+
+    **Examples:**
+
+    .. doctest::
+
+        >>> from datetime import *
+        >>> from dateutil.tz import *
+
+        >>> datetime.now()
+        datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
+
+        >>> datetime.now(tzutc())
+        datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
+
+        >>> datetime.now(tzutc()).tzname()
+        'UTC'
+
+    .. versionchanged:: 2.7.0
+        ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
+        always return the same object.
+
+        .. doctest::
+
+            >>> from dateutil.tz import tzutc, UTC
+            >>> tzutc() is tzutc()
+            True
+            >>> tzutc() is UTC
+            True
+    """
+    def utcoffset(self, dt):
+        return ZERO
+
+    def dst(self, dt):
+        return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return "UTC"
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        return False
+
+    @_validate_fromutc_inputs
+    def fromutc(self, dt):
+        """
+        Fast track version of fromutc() returns the original ``dt`` object for
+        any valid :py:class:`datetime.datetime` object.
+        """
+        return dt
+
+    def __eq__(self, other):
+        if not isinstance(other, (tzutc, tzoffset)):
+            return NotImplemented
+
+        return (isinstance(other, tzutc) or
+                (isinstance(other, tzoffset) and other._offset == ZERO))
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s()" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+#: Convenience constant providing a :class:`tzutc()` instance
+#:
+#: .. versionadded:: 2.7.0
+UTC = tzutc()
+
+
+@six.add_metaclass(_TzOffsetFactory)
+class tzoffset(datetime.tzinfo):
+    """
+    A simple class for representing a fixed offset from UTC.
+
+    :param name:
+        The timezone name, to be returned when ``tzname()`` is called.
+    :param offset:
+        The time zone offset in seconds, or (since version 2.6.0, represented
+        as a :py:class:`datetime.timedelta` object).
+    """
+    def __init__(self, name, offset):
+        self._name = name
+
+        try:
+            # Allow a timedelta
+            offset = offset.total_seconds()
+        except (TypeError, AttributeError):
+            pass
+
+        self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
+
+    def utcoffset(self, dt):
+        return self._offset
+
+    def dst(self, dt):
+        return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return self._name
+
+    @_validate_fromutc_inputs
+    def fromutc(self, dt):
+        return dt + self._offset
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        return False
+
+    def __eq__(self, other):
+        if not isinstance(other, tzoffset):
+            return NotImplemented
+
+        return self._offset == other._offset
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s(%s, %s)" % (self.__class__.__name__,
+                               repr(self._name),
+                               int(self._offset.total_seconds()))
+
+    __reduce__ = object.__reduce__
+
+
+class tzlocal(_tzinfo):
+    """
+    A :class:`tzinfo` subclass built around the ``time`` timezone functions.
+    """
+    def __init__(self):
+        super(tzlocal, self).__init__()
+
+        self._std_offset = datetime.timedelta(seconds=-time.timezone)
+        if time.daylight:
+            self._dst_offset = datetime.timedelta(seconds=-time.altzone)
+        else:
+            self._dst_offset = self._std_offset
+
+        self._dst_saved = self._dst_offset - self._std_offset
+        self._hasdst = bool(self._dst_saved)
+        self._tznames = tuple(time.tzname)
+
+    def utcoffset(self, dt):
+        if dt is None and self._hasdst:
+            return None
+
+        if self._isdst(dt):
+            return self._dst_offset
+        else:
+            return self._std_offset
+
+    def dst(self, dt):
+        if dt is None and self._hasdst:
+            return None
+
+        if self._isdst(dt):
+            return self._dst_offset - self._std_offset
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return self._tznames[self._isdst(dt)]
+
+    def is_ambiguous(self, dt):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        naive_dst = self._naive_is_dst(dt)
+        return (not naive_dst and
+                (naive_dst != self._naive_is_dst(dt - self._dst_saved)))
+
+    def _naive_is_dst(self, dt):
+        timestamp = _datetime_to_timestamp(dt)
+        return time.localtime(timestamp + time.timezone).tm_isdst
+
+    def _isdst(self, dt, fold_naive=True):
+        # We can't use mktime here. It is unstable when deciding if
+        # the hour near to a change is DST or not.
+        #
+        # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
+        #                         dt.minute, dt.second, dt.weekday(), 0, -1))
+        # return time.localtime(timestamp).tm_isdst
+        #
+        # The code above yields the following result:
+        #
+        # >>> import tz, datetime
+        # >>> t = tz.tzlocal()
+        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
+        # 'BRDT'
+        # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
+        # 'BRST'
+        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
+        # 'BRST'
+        # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
+        # 'BRDT'
+        # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
+        # 'BRDT'
+        #
+        # Here is a more stable implementation:
+        #
+        if not self._hasdst:
+            return False
+
+        # Check for ambiguous times:
+        dstval = self._naive_is_dst(dt)
+        fold = getattr(dt, 'fold', None)
+
+        if self.is_ambiguous(dt):
+            if fold is not None:
+                return not self._fold(dt)
+            else:
+                return True
+
+        return dstval
+
+    def __eq__(self, other):
+        if isinstance(other, tzlocal):
+            return (self._std_offset == other._std_offset and
+                    self._dst_offset == other._dst_offset)
+        elif isinstance(other, tzutc):
+            return (not self._hasdst and
+                    self._tznames[0] in {'UTC', 'GMT'} and
+                    self._std_offset == ZERO)
+        elif isinstance(other, tzoffset):
+            return (not self._hasdst and
+                    self._tznames[0] == other._name and
+                    self._std_offset == other._offset)
+        else:
+            return NotImplemented
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s()" % self.__class__.__name__
+
+    __reduce__ = object.__reduce__
+
+
+class _ttinfo(object):
+    __slots__ = ["offset", "delta", "isdst", "abbr",
+                 "isstd", "isgmt", "dstoffset"]
+
+    def __init__(self):
+        for attr in self.__slots__:
+            setattr(self, attr, None)
+
+    def __repr__(self):
+        l = []
+        for attr in self.__slots__:
+            value = getattr(self, attr)
+            if value is not None:
+                l.append("%s=%s" % (attr, repr(value)))
+        return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
+
+    def __eq__(self, other):
+        if not isinstance(other, _ttinfo):
+            return NotImplemented
+
+        return (self.offset == other.offset and
+                self.delta == other.delta and
+                self.isdst == other.isdst and
+                self.abbr == other.abbr and
+                self.isstd == other.isstd and
+                self.isgmt == other.isgmt and
+                self.dstoffset == other.dstoffset)
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __getstate__(self):
+        state = {}
+        for name in self.__slots__:
+            state[name] = getattr(self, name, None)
+        return state
+
+    def __setstate__(self, state):
+        for name in self.__slots__:
+            if name in state:
+                setattr(self, name, state[name])
+
+
+class _tzfile(object):
+    """
+    Lightweight class for holding the relevant transition and time zone
+    information read from binary tzfiles.
+    """
+    attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
+             'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']
+
+    def __init__(self, **kwargs):
+        for attr in self.attrs:
+            setattr(self, attr, kwargs.get(attr, None))
+
+
+class tzfile(_tzinfo):
+    """
+    This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
+    format timezone files to extract current and historical zone information.
+
+    :param fileobj:
+        This can be an opened file stream or a file name that the time zone
+        information can be read from.
+
+    :param filename:
+        This is an optional parameter specifying the source of the time zone
+        information in the event that ``fileobj`` is a file object. If omitted
+        and ``fileobj`` is a file stream, this parameter will be set either to
+        ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
+
+    See `Sources for Time Zone and Daylight Saving Time Data
+    <https://data.iana.org/time-zones/tz-link.html>`_ for more information.
+    Time zone files can be compiled from the `IANA Time Zone database files
+    <https://www.iana.org/time-zones>`_ with the `zic time zone compiler
+    <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
+
+    .. note::
+
+        Only construct a ``tzfile`` directly if you have a specific timezone
+        file on disk that you want to read into a Python ``tzinfo`` object.
+        If you want to get a ``tzfile`` representing a specific IANA zone,
+        (e.g. ``'America/New_York'``), you should call
+        :func:`dateutil.tz.gettz` with the zone identifier.
+
+
+    **Examples:**
+
+    Using the US Eastern time zone as an example, we can see that a ``tzfile``
+    provides time zone information for the standard Daylight Saving offsets:
+
+    .. testsetup:: tzfile
+
+        from dateutil.tz import gettz
+        from datetime import datetime
+
+    .. doctest:: tzfile
+
+        >>> NYC = gettz('America/New_York')
+        >>> NYC
+        tzfile('/usr/share/zoneinfo/America/New_York')
+
+        >>> print(datetime(2016, 1, 3, tzinfo=NYC))     # EST
+        2016-01-03 00:00:00-05:00
+
+        >>> print(datetime(2016, 7, 7, tzinfo=NYC))     # EDT
+        2016-07-07 00:00:00-04:00
+
+
+    The ``tzfile`` structure contains a fully history of the time zone,
+    so historical dates will also have the right offsets. For example, before
+    the adoption of the UTC standards, New York used local solar  mean time:
+
+    .. doctest:: tzfile
+
+       >>> print(datetime(1901, 4, 12, tzinfo=NYC))    # LMT
+       1901-04-12 00:00:00-04:56
+
+    And during World War II, New York was on "Eastern War Time", which was a
+    state of permanent daylight saving time:
+
+    .. doctest:: tzfile
+
+        >>> print(datetime(1944, 2, 7, tzinfo=NYC))    # EWT
+        1944-02-07 00:00:00-04:00
+
+    """
+
+    def __init__(self, fileobj, filename=None):
+        super(tzfile, self).__init__()
+
+        file_opened_here = False
+        if isinstance(fileobj, string_types):
+            self._filename = fileobj
+            fileobj = open(fileobj, 'rb')
+            file_opened_here = True
+        elif filename is not None:
+            self._filename = filename
+        elif hasattr(fileobj, "name"):
+            self._filename = fileobj.name
+        else:
+            self._filename = repr(fileobj)
+
+        if fileobj is not None:
+            if not file_opened_here:
+                fileobj = _nullcontext(fileobj)
+
+            with fileobj as file_stream:
+                tzobj = self._read_tzfile(file_stream)
+
+            self._set_tzdata(tzobj)
+
+    def _set_tzdata(self, tzobj):
+        """ Set the time zone data of this object from a _tzfile object """
+        # Copy the relevant attributes over as private attributes
+        for attr in _tzfile.attrs:
+            setattr(self, '_' + attr, getattr(tzobj, attr))
+
+    def _read_tzfile(self, fileobj):
+        out = _tzfile()
+
+        # From tzfile(5):
+        #
+        # The time zone information files used by tzset(3)
+        # begin with the magic characters "TZif" to identify
+        # them as time zone information files, followed by
+        # sixteen bytes reserved for future use, followed by
+        # six four-byte values of type long, written in a
+        # ``standard'' byte order (the high-order  byte
+        # of the value is written first).
+        if fileobj.read(4).decode() != "TZif":
+            raise ValueError("magic not found")
+
+        fileobj.read(16)
+
+        (
+            # The number of UTC/local indicators stored in the file.
+            ttisgmtcnt,
+
+            # The number of standard/wall indicators stored in the file.
+            ttisstdcnt,
+
+            # The number of leap seconds for which data is
+            # stored in the file.
+            leapcnt,
+
+            # The number of "transition times" for which data
+            # is stored in the file.
+            timecnt,
+
+            # The number of "local time types" for which data
+            # is stored in the file (must not be zero).
+            typecnt,
+
+            # The  number  of  characters  of "time zone
+            # abbreviation strings" stored in the file.
+            charcnt,
+
+        ) = struct.unpack(">6l", fileobj.read(24))
+
+        # The above header is followed by tzh_timecnt four-byte
+        # values  of  type long,  sorted  in ascending order.
+        # These values are written in ``standard'' byte order.
+        # Each is used as a transition time (as  returned  by
+        # time(2)) at which the rules for computing local time
+        # change.
+
+        if timecnt:
+            out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
+                                                    fileobj.read(timecnt*4)))
+        else:
+            out.trans_list_utc = []
+
+        # Next come tzh_timecnt one-byte values of type unsigned
+        # char; each one tells which of the different types of
+        # ``local time'' types described in the file is associated
+        # with the same-indexed transition time. These values
+        # serve as indices into an array of ttinfo structures that
+        # appears next in the file.
+
+        if timecnt:
+            out.trans_idx = struct.unpack(">%dB" % timecnt,
+                                          fileobj.read(timecnt))
+        else:
+            out.trans_idx = []
+
+        # Each ttinfo structure is written as a four-byte value
+        # for tt_gmtoff  of  type long,  in  a  standard  byte
+        # order, followed  by a one-byte value for tt_isdst
+        # and a one-byte  value  for  tt_abbrind.   In  each
+        # structure, tt_gmtoff  gives  the  number  of
+        # seconds to be added to UTC, tt_isdst tells whether
+        # tm_isdst should be set by  localtime(3),  and
+        # tt_abbrind serves  as an index into the array of
+        # time zone abbreviation characters that follow the
+        # ttinfo structure(s) in the file.
+
+        ttinfo = []
+
+        for i in range(typecnt):
+            ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
+
+        abbr = fileobj.read(charcnt).decode()
+
+        # Then there are tzh_leapcnt pairs of four-byte
+        # values, written in  standard byte  order;  the
+        # first  value  of  each pair gives the time (as
+        # returned by time(2)) at which a leap second
+        # occurs;  the  second  gives the  total  number of
+        # leap seconds to be applied after the given time.
+        # The pairs of values are sorted in ascending order
+        # by time.
+
+        # Not used, for now (but seek for correct file position)
+        if leapcnt:
+            fileobj.seek(leapcnt * 8, os.SEEK_CUR)
+
+        # Then there are tzh_ttisstdcnt standard/wall
+        # indicators, each stored as a one-byte value;
+        # they tell whether the transition times associated
+        # with local time types were specified as standard
+        # time or wall clock time, and are used when
+        # a time zone file is used in handling POSIX-style
+        # time zone environment variables.
+
+        if ttisstdcnt:
+            isstd = struct.unpack(">%db" % ttisstdcnt,
+                                  fileobj.read(ttisstdcnt))
+
+        # Finally, there are tzh_ttisgmtcnt UTC/local
+        # indicators, each stored as a one-byte value;
+        # they tell whether the transition times associated
+        # with local time types were specified as UTC or
+        # local time, and are used when a time zone file
+        # is used in handling POSIX-style time zone envi-
+        # ronment variables.
+
+        if ttisgmtcnt:
+            isgmt = struct.unpack(">%db" % ttisgmtcnt,
+                                  fileobj.read(ttisgmtcnt))
+
+        # Build ttinfo list
+        out.ttinfo_list = []
+        for i in range(typecnt):
+            gmtoff, isdst, abbrind = ttinfo[i]
+            gmtoff = _get_supported_offset(gmtoff)
+            tti = _ttinfo()
+            tti.offset = gmtoff
+            tti.dstoffset = datetime.timedelta(0)
+            tti.delta = datetime.timedelta(seconds=gmtoff)
+            tti.isdst = isdst
+            tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
+            tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
+            tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
+            out.ttinfo_list.append(tti)
+
+        # Replace ttinfo indexes for ttinfo objects.
+        out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]
+
+        # Set standard, dst, and before ttinfos. before will be
+        # used when a given time is before any transitions,
+        # and will be set to the first non-dst ttinfo, or to
+        # the first dst, if all of them are dst.
+        out.ttinfo_std = None
+        out.ttinfo_dst = None
+        out.ttinfo_before = None
+        if out.ttinfo_list:
+            if not out.trans_list_utc:
+                out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]
+            else:
+                for i in range(timecnt-1, -1, -1):
+                    tti = out.trans_idx[i]
+                    if not out.ttinfo_std and not tti.isdst:
+                        out.ttinfo_std = tti
+                    elif not out.ttinfo_dst and tti.isdst:
+                        out.ttinfo_dst = tti
+
+                    if out.ttinfo_std and out.ttinfo_dst:
+                        break
+                else:
+                    if out.ttinfo_dst and not out.ttinfo_std:
+                        out.ttinfo_std = out.ttinfo_dst
+
+                for tti in out.ttinfo_list:
+                    if not tti.isdst:
+                        out.ttinfo_before = tti
+                        break
+                else:
+                    out.ttinfo_before = out.ttinfo_list[0]
+
+        # Now fix transition times to become relative to wall time.
+        #
+        # I'm not sure about this. In my tests, the tz source file
+        # is setup to wall time, and in the binary file isstd and
+        # isgmt are off, so it should be in wall time. OTOH, it's
+        # always in gmt time. Let me know if you have comments
+        # about this.
+        lastdst = None
+        lastoffset = None
+        lastdstoffset = None
+        lastbaseoffset = None
+        out.trans_list = []
+
+        for i, tti in enumerate(out.trans_idx):
+            offset = tti.offset
+            dstoffset = 0
+
+            if lastdst is not None:
+                if tti.isdst:
+                    if not lastdst:
+                        dstoffset = offset - lastoffset
+
+                    if not dstoffset and lastdstoffset:
+                        dstoffset = lastdstoffset
+
+                    tti.dstoffset = datetime.timedelta(seconds=dstoffset)
+                    lastdstoffset = dstoffset
+
+            # If a time zone changes its base offset during a DST transition,
+            # then you need to adjust by the previous base offset to get the
+            # transition time in local time. Otherwise you use the current
+            # base offset. Ideally, I would have some mathematical proof of
+            # why this is true, but I haven't really thought about it enough.
+            baseoffset = offset - dstoffset
+            adjustment = baseoffset
+            if (lastbaseoffset is not None and baseoffset != lastbaseoffset
+                    and tti.isdst != lastdst):
+                # The base DST has changed
+                adjustment = lastbaseoffset
+
+            lastdst = tti.isdst
+            lastoffset = offset
+            lastbaseoffset = baseoffset
+
+            out.trans_list.append(out.trans_list_utc[i] + adjustment)
+
+        out.trans_idx = tuple(out.trans_idx)
+        out.trans_list = tuple(out.trans_list)
+        out.trans_list_utc = tuple(out.trans_list_utc)
+
+        return out
+
+    def _find_last_transition(self, dt, in_utc=False):
+        # If there's no list, there are no transitions to find
+        if not self._trans_list:
+            return None
+
+        timestamp = _datetime_to_timestamp(dt)
+
+        # Find where the timestamp fits in the transition list - if the
+        # timestamp is a transition time, it's part of the "after" period.
+        trans_list = self._trans_list_utc if in_utc else self._trans_list
+        idx = bisect.bisect_right(trans_list, timestamp)
+
+        # We want to know when the previous transition was, so subtract off 1
+        return idx - 1
+
+    def _get_ttinfo(self, idx):
+        # For no list or after the last transition, default to _ttinfo_std
+        if idx is None or (idx + 1) >= len(self._trans_list):
+            return self._ttinfo_std
+
+        # If there is a list and the time is before it, return _ttinfo_before
+        if idx < 0:
+            return self._ttinfo_before
+
+        return self._trans_idx[idx]
+
+    def _find_ttinfo(self, dt):
+        idx = self._resolve_ambiguous_time(dt)
+
+        return self._get_ttinfo(idx)
+
+    def fromutc(self, dt):
+        """
+        The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
+
+        :param dt:
+            A :py:class:`datetime.datetime` object.
+
+        :raises TypeError:
+            Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
+
+        :raises ValueError:
+            Raised if this is called with a ``dt`` which does not have this
+            ``tzinfo`` attached.
+
+        :return:
+            Returns a :py:class:`datetime.datetime` object representing the
+            wall time in ``self``'s time zone.
+        """
+        # These isinstance checks are in datetime.tzinfo, so we'll preserve
+        # them, even if we don't care about duck typing.
+        if not isinstance(dt, datetime.datetime):
+            raise TypeError("fromutc() requires a datetime argument")
+
+        if dt.tzinfo is not self:
+            raise ValueError("dt.tzinfo is not self")
+
+        # First treat UTC as wall time and get the transition we're in.
+        idx = self._find_last_transition(dt, in_utc=True)
+        tti = self._get_ttinfo(idx)
+
+        dt_out = dt + datetime.timedelta(seconds=tti.offset)
+
+        fold = self.is_ambiguous(dt_out, idx=idx)
+
+        return enfold(dt_out, fold=int(fold))
+
+    def is_ambiguous(self, dt, idx=None):
+        """
+        Whether or not the "wall time" of a given datetime is ambiguous in this
+        zone.
+
+        :param dt:
+            A :py:class:`datetime.datetime`, naive or time zone aware.
+
+
+        :return:
+            Returns ``True`` if ambiguous, ``False`` otherwise.
+
+        .. versionadded:: 2.6.0
+        """
+        if idx is None:
+            idx = self._find_last_transition(dt)
+
+        # Calculate the difference in offsets from current to previous
+        timestamp = _datetime_to_timestamp(dt)
+        tti = self._get_ttinfo(idx)
+
+        if idx is None or idx <= 0:
+            return False
+
+        od = self._get_ttinfo(idx - 1).offset - tti.offset
+        tt = self._trans_list[idx]          # Transition time
+
+        return timestamp < tt + od
+
+    def _resolve_ambiguous_time(self, dt):
+        idx = self._find_last_transition(dt)
+
+        # If we have no transitions, return the index
+        _fold = self._fold(dt)
+        if idx is None or idx == 0:
+            return idx
+
+        # If it's ambiguous and we're in a fold, shift to a different index.
+        idx_offset = int(not _fold and self.is_ambiguous(dt, idx))
+
+        return idx - idx_offset
+
+    def utcoffset(self, dt):
+        if dt is None:
+            return None
+
+        if not self._ttinfo_std:
+            return ZERO
+
+        return self._find_ttinfo(dt).delta
+
+    def dst(self, dt):
+        if dt is None:
+            return None
+
+        if not self._ttinfo_dst:
+            return ZERO
+
+        tti = self._find_ttinfo(dt)
+
+        if not tti.isdst:
+            return ZERO
+
+        # The documentation says that utcoffset()-dst() must
+        # be constant for every dt.
+        return tti.dstoffset
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        if not self._ttinfo_std or dt is None:
+            return None
+        return self._find_ttinfo(dt).abbr
+
+    def __eq__(self, other):
+        if not isinstance(other, tzfile):
+            return NotImplemented
+        return (self._trans_list == other._trans_list and
+                self._trans_idx == other._trans_idx and
+                self._ttinfo_list == other._ttinfo_list)
+
+    __hash__ = None
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
+
+    def __reduce__(self):
+        return self.__reduce_ex__(None)
+
+    def __reduce_ex__(self, protocol):
+        return (self.__class__, (None, self._filename), self.__dict__)
+
+
+class tzrange(tzrangebase):
+    """
+    The ``tzrange`` object is a time zone specified by a set of offsets and
+    abbreviations, equivalent to the way the ``TZ`` variable can be specified
+    in POSIX-like systems, but using Python delta objects to specify DST
+    start, end and offsets.
+
+    :param stdabbr:
+        The abbreviation for standard time (e.g. ``'EST'``).
+
+    :param stdoffset:
+        An integer or :class:`datetime.timedelta` object or equivalent
+        specifying the base offset from UTC.
+
+        If unspecified, +00:00 is used.
+
+    :param dstabbr:
+        The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
+
+        If specified, with no other DST information, DST is assumed to occur
+        and the default behavior or ``dstoffset``, ``start`` and ``end`` is
+        used. If unspecified and no other DST information is specified, it
+        is assumed that this zone has no DST.
+
+        If this is unspecified and other DST information is *is* specified,
+        DST occurs in the zone but the time zone abbreviation is left
+        unchanged.
+
+    :param dstoffset:
+        A an integer or :class:`datetime.timedelta` object or equivalent
+        specifying the UTC offset during DST. If unspecified and any other DST
+        information is specified, it is assumed to be the STD offset +1 hour.
+
+    :param start:
+        A :class:`relativedelta.relativedelta` object or equivalent specifying
+        the time and time of year that daylight savings time starts. To
+        specify, for example, that DST starts at 2AM on the 2nd Sunday in
+        March, pass:
+
+            ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
+
+        If unspecified and any other DST information is specified, the default
+        value is 2 AM on the first Sunday in April.
+
+    :param end:
+        A :class:`relativedelta.relativedelta` object or equivalent
+        representing the time and time of year that daylight savings time
+        ends, with the same specification method as in ``start``. One note is
+        that this should point to the first time in the *standard* zone, so if
+        a transition occurs at 2AM in the DST zone and the clocks are set back
+        1 hour to 1AM, set the ``hours`` parameter to +1.
+
+
+    **Examples:**
+
+    .. testsetup:: tzrange
+
+        from dateutil.tz import tzrange, tzstr
+
+    .. doctest:: tzrange
+
+        >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
+        True
+
+        >>> from dateutil.relativedelta import *
+        >>> range1 = tzrange("EST", -18000, "EDT")
+        >>> range2 = tzrange("EST", -18000, "EDT", -14400,
+        ...                  relativedelta(hours=+2, month=4, day=1,
+        ...                                weekday=SU(+1)),
+        ...                  relativedelta(hours=+1, month=10, day=31,
+        ...                                weekday=SU(-1)))
+        >>> tzstr('EST5EDT') == range1 == range2
+        True
+
+    """
+    def __init__(self, stdabbr, stdoffset=None,
+                 dstabbr=None, dstoffset=None,
+                 start=None, end=None):
+
+        global relativedelta
+        from dateutil import relativedelta
+
+        self._std_abbr = stdabbr
+        self._dst_abbr = dstabbr
+
+        try:
+            stdoffset = stdoffset.total_seconds()
+        except (TypeError, AttributeError):
+            pass
+
+        try:
+            dstoffset = dstoffset.total_seconds()
+        except (TypeError, AttributeError):
+            pass
+
+        if stdoffset is not None:
+            self._std_offset = datetime.timedelta(seconds=stdoffset)
+        else:
+            self._std_offset = ZERO
+
+        if dstoffset is not None:
+            self._dst_offset = datetime.timedelta(seconds=dstoffset)
+        elif dstabbr and stdoffset is not None:
+            self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
+        else:
+            self._dst_offset = ZERO
+
+        if dstabbr and start is None:
+            self._start_delta = relativedelta.relativedelta(
+                hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
+        else:
+            self._start_delta = start
+
+        if dstabbr and end is None:
+            self._end_delta = relativedelta.relativedelta(
+                hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
+        else:
+            self._end_delta = end
+
+        self._dst_base_offset_ = self._dst_offset - self._std_offset
+        self.hasdst = bool(self._start_delta)
+
+    def transitions(self, year):
+        """
+        For a given year, get the DST on and off transition times, expressed
+        always on the standard time side. For zones with no transitions, this
+        function returns ``None``.
+
+        :param year:
+            The year whose transitions you would like to query.
+
+        :return:
+            Returns a :class:`tuple` of :class:`datetime.datetime` objects,
+            ``(dston, dstoff)`` for zones with an annual DST transition, or
+            ``None`` for fixed offset zones.
+        """
+        if not self.hasdst:
+            return None
+
+        base_year = datetime.datetime(year, 1, 1)
+
+        start = base_year + self._start_delta
+        end = base_year + self._end_delta
+
+        return (start, end)
+
+    def __eq__(self, other):
+        if not isinstance(other, tzrange):
+            return NotImplemented
+
+        return (self._std_abbr == other._std_abbr and
+                self._dst_abbr == other._dst_abbr and
+                self._std_offset == other._std_offset and
+                self._dst_offset == other._dst_offset and
+                self._start_delta == other._start_delta and
+                self._end_delta == other._end_delta)
+
+    @property
+    def _dst_base_offset(self):
+        return self._dst_base_offset_
+
+
+@six.add_metaclass(_TzStrFactory)
+class tzstr(tzrange):
+    """
+    ``tzstr`` objects are time zone objects specified by a time-zone string as
+    it would be passed to a ``TZ`` variable on POSIX-style systems (see
+    the `GNU C Library: TZ Variable`_ for more details).
+
+    There is one notable exception, which is that POSIX-style time zones use an
+    inverted offset format, so normally ``GMT+3`` would be parsed as an offset
+    3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
+    offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
+    behavior, pass a ``True`` value to ``posix_offset``.
+
+    The :class:`tzrange` object provides the same functionality, but is
+    specified using :class:`relativedelta.relativedelta` objects. rather than
+    strings.
+
+    :param s:
+        A time zone string in ``TZ`` variable format. This can be a
+        :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
+        :class:`unicode`) or a stream emitting unicode characters
+        (e.g. :class:`StringIO`).
+
+    :param posix_offset:
+        Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
+        ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
+        POSIX standard.
+
+    .. caution::
+
+        Prior to version 2.7.0, this function also supported time zones
+        in the format:
+
+            * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
+            * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
+
+        This format is non-standard and has been deprecated; this function
+        will raise a :class:`DeprecatedTZFormatWarning` until
+        support is removed in a future version.
+
+    .. _`GNU C Library: TZ Variable`:
+        https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
+    """
+    def __init__(self, s, posix_offset=False):
+        global parser
+        from dateutil.parser import _parser as parser
+
+        self._s = s
+
+        res = parser._parsetz(s)
+        if res is None or res.any_unused_tokens:
+            raise ValueError("unknown string format")
+
+        # Here we break the compatibility with the TZ variable handling.
+        # GMT-3 actually *means* the timezone -3.
+        if res.stdabbr in ("GMT", "UTC") and not posix_offset:
+            res.stdoffset *= -1
+
+        # We must initialize it first, since _delta() needs
+        # _std_offset and _dst_offset set. Use False in start/end
+        # to avoid building it two times.
+        tzrange.__init__(self, res.stdabbr, res.stdoffset,
+                         res.dstabbr, res.dstoffset,
+                         start=False, end=False)
+
+        if not res.dstabbr:
+            self._start_delta = None
+            self._end_delta = None
+        else:
+            self._start_delta = self._delta(res.start)
+            if self._start_delta:
+                self._end_delta = self._delta(res.end, isend=1)
+
+        self.hasdst = bool(self._start_delta)
+
+    def _delta(self, x, isend=0):
+        from dateutil import relativedelta
+        kwargs = {}
+        if x.month is not None:
+            kwargs["month"] = x.month
+            if x.weekday is not None:
+                kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
+                if x.week > 0:
+                    kwargs["day"] = 1
+                else:
+                    kwargs["day"] = 31
+            elif x.day:
+                kwargs["day"] = x.day
+        elif x.yday is not None:
+            kwargs["yearday"] = x.yday
+        elif x.jyday is not None:
+            kwargs["nlyearday"] = x.jyday
+        if not kwargs:
+            # Default is to start on first sunday of april, and end
+            # on last sunday of october.
+            if not isend:
+                kwargs["month"] = 4
+                kwargs["day"] = 1
+                kwargs["weekday"] = relativedelta.SU(+1)
+            else:
+                kwargs["month"] = 10
+                kwargs["day"] = 31
+                kwargs["weekday"] = relativedelta.SU(-1)
+        if x.time is not None:
+            kwargs["seconds"] = x.time
+        else:
+            # Default is 2AM.
+            kwargs["seconds"] = 7200
+        if isend:
+            # Convert to standard time, to follow the documented way
+            # of working with the extra hour. See the documentation
+            # of the tzinfo class.
+            delta = self._dst_offset - self._std_offset
+            kwargs["seconds"] -= delta.seconds + delta.days * 86400
+        return relativedelta.relativedelta(**kwargs)
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._s))
+
+
+class _tzicalvtzcomp(object):
+    def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
+                 tzname=None, rrule=None):
+        self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
+        self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
+        self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
+        self.isdst = isdst
+        self.tzname = tzname
+        self.rrule = rrule
+
+
+class _tzicalvtz(_tzinfo):
+    def __init__(self, tzid, comps=[]):
+        super(_tzicalvtz, self).__init__()
+
+        self._tzid = tzid
+        self._comps = comps
+        self._cachedate = []
+        self._cachecomp = []
+        self._cache_lock = _thread.allocate_lock()
+
+    def _find_comp(self, dt):
+        if len(self._comps) == 1:
+            return self._comps[0]
+
+        dt = dt.replace(tzinfo=None)
+
+        try:
+            with self._cache_lock:
+                return self._cachecomp[self._cachedate.index(
+                    (dt, self._fold(dt)))]
+        except ValueError:
+            pass
+
+        lastcompdt = None
+        lastcomp = None
+
+        for comp in self._comps:
+            compdt = self._find_compdt(comp, dt)
+
+            if compdt and (not lastcompdt or lastcompdt < compdt):
+                lastcompdt = compdt
+                lastcomp = comp
+
+        if not lastcomp:
+            # RFC says nothing about what to do when a given
+            # time is before the first onset date. We'll look for the
+            # first standard component, or the first component, if
+            # none is found.
+            for comp in self._comps:
+                if not comp.isdst:
+                    lastcomp = comp
+                    break
+            else:
+                lastcomp = comp[0]
+
+        with self._cache_lock:
+            self._cachedate.insert(0, (dt, self._fold(dt)))
+            self._cachecomp.insert(0, lastcomp)
+
+            if len(self._cachedate) > 10:
+                self._cachedate.pop()
+                self._cachecomp.pop()
+
+        return lastcomp
+
+    def _find_compdt(self, comp, dt):
+        if comp.tzoffsetdiff < ZERO and self._fold(dt):
+            dt -= comp.tzoffsetdiff
+
+        compdt = comp.rrule.before(dt, inc=True)
+
+        return compdt
+
+    def utcoffset(self, dt):
+        if dt is None:
+            return None
+
+        return self._find_comp(dt).tzoffsetto
+
+    def dst(self, dt):
+        comp = self._find_comp(dt)
+        if comp.isdst:
+            return comp.tzoffsetdiff
+        else:
+            return ZERO
+
+    @tzname_in_python2
+    def tzname(self, dt):
+        return self._find_comp(dt).tzname
+
+    def __repr__(self):
+        return "<tzicalvtz %s>" % repr(self._tzid)
+
+    __reduce__ = object.__reduce__
+
+
+class tzical(object):
+    """
+    This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
+    as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
+
+    :param `fileobj`:
+        A file or stream in iCalendar format, which should be UTF-8 encoded
+        with CRLF endings.
+
+    .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
+    """
+    def __init__(self, fileobj):
+        global rrule
+        from dateutil import rrule
+
+        if isinstance(fileobj, string_types):
+            self._s = fileobj
+            # ical should be encoded in UTF-8 with CRLF
+            fileobj = open(fileobj, 'r')
+        else:
+            self._s = getattr(fileobj, 'name', repr(fileobj))
+            fileobj = _nullcontext(fileobj)
+
+        self._vtz = {}
+
+        with fileobj as fobj:
+            self._parse_rfc(fobj.read())
+
+    def keys(self):
+        """
+        Retrieves the available time zones as a list.
+        """
+        return list(self._vtz.keys())
+
+    def get(self, tzid=None):
+        """
+        Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
+
+        :param tzid:
+            If there is exactly one time zone available, omitting ``tzid``
+            or passing :py:const:`None` value returns it. Otherwise a valid
+            key (which can be retrieved from :func:`keys`) is required.
+
+        :raises ValueError:
+            Raised if ``tzid`` is not specified but there are either more
+            or fewer than 1 zone defined.
+
+        :returns:
+            Returns either a :py:class:`datetime.tzinfo` object representing
+            the relevant time zone or :py:const:`None` if the ``tzid`` was
+            not found.
+        """
+        if tzid is None:
+            if len(self._vtz) == 0:
+                raise ValueError("no timezones defined")
+            elif len(self._vtz) > 1:
+                raise ValueError("more than one timezone available")
+            tzid = next(iter(self._vtz))
+
+        return self._vtz.get(tzid)
+
+    def _parse_offset(self, s):
+        s = s.strip()
+        if not s:
+            raise ValueError("empty offset")
+        if s[0] in ('+', '-'):
+            signal = (-1, +1)[s[0] == '+']
+            s = s[1:]
+        else:
+            signal = +1
+        if len(s) == 4:
+            return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
+        elif len(s) == 6:
+            return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
+        else:
+            raise ValueError("invalid offset: " + s)
+
+    def _parse_rfc(self, s):
+        lines = s.splitlines()
+        if not lines:
+            raise ValueError("empty string")
+
+        # Unfold
+        i = 0
+        while i < len(lines):
+            line = lines[i].rstrip()
+            if not line:
+                del lines[i]
+            elif i > 0 and line[0] == " ":
+                lines[i-1] += line[1:]
+                del lines[i]
+            else:
+                i += 1
+
+        tzid = None
+        comps = []
+        invtz = False
+        comptype = None
+        for line in lines:
+            if not line:
+                continue
+            name, value = line.split(':', 1)
+            parms = name.split(';')
+            if not parms:
+                raise ValueError("empty property name")
+            name = parms[0].upper()
+            parms = parms[1:]
+            if invtz:
+                if name == "BEGIN":
+                    if value in ("STANDARD", "DAYLIGHT"):
+                        # Process component
+                        pass
+                    else:
+                        raise ValueError("unknown component: "+value)
+                    comptype = value
+                    founddtstart = False
+                    tzoffsetfrom = None
+                    tzoffsetto = None
+                    rrulelines = []
+                    tzname = None
+                elif name == "END":
+                    if value == "VTIMEZONE":
+                        if comptype:
+                            raise ValueError("component not closed: "+comptype)
+                        if not tzid:
+                            raise ValueError("mandatory TZID not found")
+                        if not comps:
+                            raise ValueError(
+                                "at least one component is needed")
+                        # Process vtimezone
+                        self._vtz[tzid] = _tzicalvtz(tzid, comps)
+                        invtz = False
+                    elif value == comptype:
+                        if not founddtstart:
+                            raise ValueError("mandatory DTSTART not found")
+                        if tzoffsetfrom is None:
+                            raise ValueError(
+                                "mandatory TZOFFSETFROM not found")
+                        if tzoffsetto is None:
+                            raise ValueError(
+                                "mandatory TZOFFSETFROM not found")
+                        # Process component
+                        rr = None
+                        if rrulelines:
+                            rr = rrule.rrulestr("\n".join(rrulelines),
+                                                compatible=True,
+                                                ignoretz=True,
+                                                cache=True)
+                        comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
+                                              (comptype == "DAYLIGHT"),
+                                              tzname, rr)
+                        comps.append(comp)
+                        comptype = None
+                    else:
+                        raise ValueError("invalid component end: "+value)
+                elif comptype:
+                    if name == "DTSTART":
+                        # DTSTART in VTIMEZONE takes a subset of valid RRULE
+                        # values under RFC 5545.
+                        for parm in parms:
+                            if parm != 'VALUE=DATE-TIME':
+                                msg = ('Unsupported DTSTART param in ' +
+                                       'VTIMEZONE: ' + parm)
+                                raise ValueError(msg)
+                        rrulelines.append(line)
+                        founddtstart = True
+                    elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
+                        rrulelines.append(line)
+                    elif name == "TZOFFSETFROM":
+                        if parms:
+                            raise ValueError(
+                                "unsupported %s parm: %s " % (name, parms[0]))
+                        tzoffsetfrom = self._parse_offset(value)
+                    elif name == "TZOFFSETTO":
+                        if parms:
+                            raise ValueError(
+                                "unsupported TZOFFSETTO parm: "+parms[0])
+                        tzoffsetto = self._parse_offset(value)
+                    elif name == "TZNAME":
+                        if parms:
+                            raise ValueError(
+                                "unsupported TZNAME parm: "+parms[0])
+                        tzname = value
+                    elif name == "COMMENT":
+                        pass
+                    else:
+                        raise ValueError("unsupported property: "+name)
+                else:
+                    if name == "TZID":
+                        if parms:
+                            raise ValueError(
+                                "unsupported TZID parm: "+parms[0])
+                        tzid = value
+                    elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
+                        pass
+                    else:
+                        raise ValueError("unsupported property: "+name)
+            elif name == "BEGIN" and value == "VTIMEZONE":
+                tzid = None
+                comps = []
+                invtz = True
+
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._s))
+
+
+if sys.platform != "win32":
+    TZFILES = ["/etc/localtime", "localtime"]
+    TZPATHS = ["/usr/share/zoneinfo",
+               "/usr/lib/zoneinfo",
+               "/usr/share/lib/zoneinfo",
+               "/etc/zoneinfo"]
+else:
+    TZFILES = []
+    TZPATHS = []
+
+
+def __get_gettz():
+    tzlocal_classes = (tzlocal,)
+    if tzwinlocal is not None:
+        tzlocal_classes += (tzwinlocal,)
+
+    class GettzFunc(object):
+        """
+        Retrieve a time zone object from a string representation
+
+        This function is intended to retrieve the :py:class:`tzinfo` subclass
+        that best represents the time zone that would be used if a POSIX
+        `TZ variable`_ were set to the same value.
+
+        If no argument or an empty string is passed to ``gettz``, local time
+        is returned:
+
+        .. code-block:: python3
+
+            >>> gettz()
+            tzfile('/etc/localtime')
+
+        This function is also the preferred way to map IANA tz database keys
+        to :class:`tzfile` objects:
+
+        .. code-block:: python3
+
+            >>> gettz('Pacific/Kiritimati')
+            tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
+
+        On Windows, the standard is extended to include the Windows-specific
+        zone names provided by the operating system:
+
+        .. code-block:: python3
+
+            >>> gettz('Egypt Standard Time')
+            tzwin('Egypt Standard Time')
+
+        Passing a GNU ``TZ`` style string time zone specification returns a
+        :class:`tzstr` object:
+
+        .. code-block:: python3
+
+            >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
+            tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
+
+        :param name:
+            A time zone name (IANA, or, on Windows, Windows keys), location of
+            a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
+            specifier. An empty string, no argument or ``None`` is interpreted
+            as local time.
+
+        :return:
+            Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
+            subclasses.
+
+        .. versionchanged:: 2.7.0
+
+            After version 2.7.0, any two calls to ``gettz`` using the same
+            input strings will return the same object:
+
+            .. code-block:: python3
+
+                >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
+                True
+
+            In addition to improving performance, this ensures that
+            `"same zone" semantics`_ are used for datetimes in the same zone.
+
+
+        .. _`TZ variable`:
+            https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
+
+        .. _`"same zone" semantics`:
+            https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
+        """
+        def __init__(self):
+
+            self.__instances = weakref.WeakValueDictionary()
+            self.__strong_cache_size = 8
+            self.__strong_cache = OrderedDict()
+            self._cache_lock = _thread.allocate_lock()
+
+        def __call__(self, name=None):
+            with self._cache_lock:
+                rv = self.__instances.get(name, None)
+
+                if rv is None:
+                    rv = self.nocache(name=name)
+                    if not (name is None
+                            or isinstance(rv, tzlocal_classes)
+                            or rv is None):
+                        # tzlocal is slightly more complicated than the other
+                        # time zone providers because it depends on environment
+                        # at construction time, so don't cache that.
+                        #
+                        # We also cannot store weak references to None, so we
+                        # will also not store that.
+                        self.__instances[name] = rv
+                    else:
+                        # No need for strong caching, return immediately
+                        return rv
+
+                self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
+
+                if len(self.__strong_cache) > self.__strong_cache_size:
+                    self.__strong_cache.popitem(last=False)
+
+            return rv
+
+        def set_cache_size(self, size):
+            with self._cache_lock:
+                self.__strong_cache_size = size
+                while len(self.__strong_cache) > size:
+                    self.__strong_cache.popitem(last=False)
+
+        def cache_clear(self):
+            with self._cache_lock:
+                self.__instances = weakref.WeakValueDictionary()
+                self.__strong_cache.clear()
+
+        @staticmethod
+        def nocache(name=None):
+            """A non-cached version of gettz"""
+            tz = None
+            if not name:
+                try:
+                    name = os.environ["TZ"]
+                except KeyError:
+                    pass
+            if name is None or name == ":":
+                for filepath in TZFILES:
+                    if not os.path.isabs(filepath):
+                        filename = filepath
+                        for path in TZPATHS:
+                            filepath = os.path.join(path, filename)
+                            if os.path.isfile(filepath):
+                                break
+                        else:
+                            continue
+                    if os.path.isfile(filepath):
+                        try:
+                            tz = tzfile(filepath)
+                            break
+                        except (IOError, OSError, ValueError):
+                            pass
+                else:
+                    tz = tzlocal()
+            else:
+                try:
+                    if name.startswith(":"):
+                        name = name[1:]
+                except TypeError as e:
+                    if isinstance(name, bytes):
+                        new_msg = "gettz argument should be str, not bytes"
+                        six.raise_from(TypeError(new_msg), e)
+                    else:
+                        raise
+                if os.path.isabs(name):
+                    if os.path.isfile(name):
+                        tz = tzfile(name)
+                    else:
+                        tz = None
+                else:
+                    for path in TZPATHS:
+                        filepath = os.path.join(path, name)
+                        if not os.path.isfile(filepath):
+                            filepath = filepath.replace(' ', '_')
+                            if not os.path.isfile(filepath):
+                                continue
+                        try:
+                            tz = tzfile(filepath)
+                            break
+                        except (IOError, OSError, ValueError):
+                            pass
+                    else:
+                        tz = None
+                        if tzwin is not None:
+                            try:
+                                tz = tzwin(name)
+                            except (WindowsError, UnicodeEncodeError):
+                                # UnicodeEncodeError is for Python 2.7 compat
+                                tz = None
+
+                        if not tz:
+                            from dateutil.zoneinfo import get_zonefile_instance
+                            tz = get_zonefile_instance().get(name)
+
+                        if not tz:
+                            for c in name:
+                                # name is not a tzstr unless it has at least
+                                # one offset. For short values of "name", an
+                                # explicit for loop seems to be the fastest way
+                                # To determine if a string contains a digit
+                                if c in "0123456789":
+                                    try:
+                                        tz = tzstr(name)
+                                    except ValueError:
+                                        pass
+                                    break
+                            else:
+                                if name in ("GMT", "UTC"):
+                                    tz = UTC
+                                elif name in time.tzname:
+                                    tz = tzlocal()
+            return tz
+
+    return GettzFunc()
+
+
+gettz = __get_gettz()
+del __get_gettz
+
+
+def datetime_exists(dt, tz=None):
+    """
+    Given a datetime and a time zone, determine whether or not a given datetime
+    would fall in a gap.
+
+    :param dt:
+        A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
+        is provided.)
+
+    :param tz:
+        A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
+        ``None`` or not provided, the datetime's own time zone will be used.
+
+    :return:
+        Returns a boolean value whether or not the "wall time" exists in
+        ``tz``.
+
+    .. versionadded:: 2.7.0
+    """
+    if tz is None:
+        if dt.tzinfo is None:
+            raise ValueError('Datetime is naive and no time zone provided.')
+        tz = dt.tzinfo
+
+    dt = dt.replace(tzinfo=None)
+
+    # This is essentially a test of whether or not the datetime can survive
+    # a round trip to UTC.
+    dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
+    dt_rt = dt_rt.replace(tzinfo=None)
+
+    return dt == dt_rt
+
+
+def datetime_ambiguous(dt, tz=None):
+    """
+    Given a datetime and a time zone, determine whether or not a given datetime
+    is ambiguous (i.e if there are two times differentiated only by their DST
+    status).
+
+    :param dt:
+        A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
+        is provided.)
+
+    :param tz:
+        A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
+        ``None`` or not provided, the datetime's own time zone will be used.
+
+    :return:
+        Returns a boolean value whether or not the "wall time" is ambiguous in
+        ``tz``.
+
+    .. versionadded:: 2.6.0
+    """
+    if tz is None:
+        if dt.tzinfo is None:
+            raise ValueError('Datetime is naive and no time zone provided.')
+
+        tz = dt.tzinfo
+
+    # If a time zone defines its own "is_ambiguous" function, we'll use that.
+    is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
+    if is_ambiguous_fn is not None:
+        try:
+            return tz.is_ambiguous(dt)
+        except Exception:
+            pass
+
+    # If it doesn't come out and tell us it's ambiguous, we'll just check if
+    # the fold attribute has any effect on this particular date and time.
+    dt = dt.replace(tzinfo=tz)
+    wall_0 = enfold(dt, fold=0)
+    wall_1 = enfold(dt, fold=1)
+
+    same_offset = wall_0.utcoffset() == wall_1.utcoffset()
+    same_dst = wall_0.dst() == wall_1.dst()
+
+    return not (same_offset and same_dst)
+
+
+def resolve_imaginary(dt):
+    """
+    Given a datetime that may be imaginary, return an existing datetime.
+
+    This function assumes that an imaginary datetime represents what the
+    wall time would be in a zone had the offset transition not occurred, so
+    it will always fall forward by the transition's change in offset.
+
+    .. doctest::
+
+        >>> from dateutil import tz
+        >>> from datetime import datetime
+        >>> NYC = tz.gettz('America/New_York')
+        >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
+        2017-03-12 03:30:00-04:00
+
+        >>> KIR = tz.gettz('Pacific/Kiritimati')
+        >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
+        1995-01-02 12:30:00+14:00
+
+    As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
+    existing datetime, so a round-trip to and from UTC is sufficient to get
+    an extant datetime, however, this generally "falls back" to an earlier time
+    rather than falling forward to the STD side (though no guarantees are made
+    about this behavior).
+
+    :param dt:
+        A :class:`datetime.datetime` which may or may not exist.
+
+    :return:
+        Returns an existing :class:`datetime.datetime`. If ``dt`` was not
+        imaginary, the datetime returned is guaranteed to be the same object
+        passed to the function.
+
+    .. versionadded:: 2.7.0
+    """
+    if dt.tzinfo is not None and not datetime_exists(dt):
+
+        curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
+        old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
+
+        dt += curr_offset - old_offset
+
+    return dt
+
+
+def _datetime_to_timestamp(dt):
+    """
+    Convert a :class:`datetime.datetime` object to an epoch timestamp in
+    seconds since January 1, 1970, ignoring the time zone.
+    """
+    return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
+
+
+if sys.version_info >= (3, 6):
+    def _get_supported_offset(second_offset):
+        return second_offset
+else:
+    def _get_supported_offset(second_offset):
+        # For python pre-3.6, round to full-minutes if that's not the case.
+        # Python's datetime doesn't accept sub-minute timezones. Check
+        # http://python.org/sf/1447945 or https://bugs.python.org/issue5288
+        # for some information.
+        old_offset = second_offset
+        calculated_offset = 60 * ((second_offset + 30) // 60)
+        return calculated_offset
+
+
+try:
+    # Python 3.7 feature
+    from contextlib import nullcontext as _nullcontext
+except ImportError:
+    class _nullcontext(object):
+        """
+        Class for wrapping contexts so that they are passed through in a
+        with statement.
+        """
+        def __init__(self, context):
+            self.context = context
+
+        def __enter__(self):
+            return self.context
+
+        def __exit__(*args, **kwargs):
+            pass
+
+# vim:ts=4:sw=4:et

+ 370 - 0
venv/lib/python3.8/site-packages/dateutil/tz/win.py

@@ -0,0 +1,370 @@
+# -*- coding: utf-8 -*-
+"""
+This module provides an interface to the native time zone data on Windows,
+including :py:class:`datetime.tzinfo` implementations.
+
+Attempting to import this module on a non-Windows platform will raise an
+:py:obj:`ImportError`.
+"""
+# This code was originally contributed by Jeffrey Harris.
+import datetime
+import struct
+
+from six.moves import winreg
+from six import text_type
+
+try:
+    import ctypes
+    from ctypes import wintypes
+except ValueError:
+    # ValueError is raised on non-Windows systems for some horrible reason.
+    raise ImportError("Running tzwin on non-Windows system")
+
+from ._common import tzrangebase
+
+__all__ = ["tzwin", "tzwinlocal", "tzres"]
+
+ONEWEEK = datetime.timedelta(7)
+
+TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
+TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
+TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
+
+
+def _settzkeyname():
+    handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+    try:
+        winreg.OpenKey(handle, TZKEYNAMENT).Close()
+        TZKEYNAME = TZKEYNAMENT
+    except WindowsError:
+        TZKEYNAME = TZKEYNAME9X
+    handle.Close()
+    return TZKEYNAME
+
+
+TZKEYNAME = _settzkeyname()
+
+
+class tzres(object):
+    """
+    Class for accessing ``tzres.dll``, which contains timezone name related
+    resources.
+
+    .. versionadded:: 2.5.0
+    """
+    p_wchar = ctypes.POINTER(wintypes.WCHAR)        # Pointer to a wide char
+
+    def __init__(self, tzres_loc='tzres.dll'):
+        # Load the user32 DLL so we can load strings from tzres
+        user32 = ctypes.WinDLL('user32')
+
+        # Specify the LoadStringW function
+        user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
+                                       wintypes.UINT,
+                                       wintypes.LPWSTR,
+                                       ctypes.c_int)
+
+        self.LoadStringW = user32.LoadStringW
+        self._tzres = ctypes.WinDLL(tzres_loc)
+        self.tzres_loc = tzres_loc
+
+    def load_name(self, offset):
+        """
+        Load a timezone name from a DLL offset (integer).
+
+        >>> from dateutil.tzwin import tzres
+        >>> tzr = tzres()
+        >>> print(tzr.load_name(112))
+        'Eastern Standard Time'
+
+        :param offset:
+            A positive integer value referring to a string from the tzres dll.
+
+        .. note::
+
+            Offsets found in the registry are generally of the form
+            ``@tzres.dll,-114``. The offset in this case is 114, not -114.
+
+        """
+        resource = self.p_wchar()
+        lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
+        nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
+        return resource[:nchar]
+
+    def name_from_string(self, tzname_str):
+        """
+        Parse strings as returned from the Windows registry into the time zone
+        name as defined in the registry.
+
+        >>> from dateutil.tzwin import tzres
+        >>> tzr = tzres()
+        >>> print(tzr.name_from_string('@tzres.dll,-251'))
+        'Dateline Daylight Time'
+        >>> print(tzr.name_from_string('Eastern Standard Time'))
+        'Eastern Standard Time'
+
+        :param tzname_str:
+            A timezone name string as returned from a Windows registry key.
+
+        :return:
+            Returns the localized timezone string from tzres.dll if the string
+            is of the form `@tzres.dll,-offset`, else returns the input string.
+        """
+        if not tzname_str.startswith('@'):
+            return tzname_str
+
+        name_splt = tzname_str.split(',-')
+        try:
+            offset = int(name_splt[1])
+        except:
+            raise ValueError("Malformed timezone string.")
+
+        return self.load_name(offset)
+
+
+class tzwinbase(tzrangebase):
+    """tzinfo class based on win32's timezones available in the registry."""
+    def __init__(self):
+        raise NotImplementedError('tzwinbase is an abstract base class')
+
+    def __eq__(self, other):
+        # Compare on all relevant dimensions, including name.
+        if not isinstance(other, tzwinbase):
+            return NotImplemented
+
+        return  (self._std_offset == other._std_offset and
+                 self._dst_offset == other._dst_offset and
+                 self._stddayofweek == other._stddayofweek and
+                 self._dstdayofweek == other._dstdayofweek and
+                 self._stdweeknumber == other._stdweeknumber and
+                 self._dstweeknumber == other._dstweeknumber and
+                 self._stdhour == other._stdhour and
+                 self._dsthour == other._dsthour and
+                 self._stdminute == other._stdminute and
+                 self._dstminute == other._dstminute and
+                 self._std_abbr == other._std_abbr and
+                 self._dst_abbr == other._dst_abbr)
+
+    @staticmethod
+    def list():
+        """Return a list of all time zones known to the system."""
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
+                result = [winreg.EnumKey(tzkey, i)
+                          for i in range(winreg.QueryInfoKey(tzkey)[0])]
+        return result
+
+    def display(self):
+        """
+        Return the display name of the time zone.
+        """
+        return self._display
+
+    def transitions(self, year):
+        """
+        For a given year, get the DST on and off transition times, expressed
+        always on the standard time side. For zones with no transitions, this
+        function returns ``None``.
+
+        :param year:
+            The year whose transitions you would like to query.
+
+        :return:
+            Returns a :class:`tuple` of :class:`datetime.datetime` objects,
+            ``(dston, dstoff)`` for zones with an annual DST transition, or
+            ``None`` for fixed offset zones.
+        """
+
+        if not self.hasdst:
+            return None
+
+        dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
+                               self._dsthour, self._dstminute,
+                               self._dstweeknumber)
+
+        dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
+                                self._stdhour, self._stdminute,
+                                self._stdweeknumber)
+
+        # Ambiguous dates default to the STD side
+        dstoff -= self._dst_base_offset
+
+        return dston, dstoff
+
+    def _get_hasdst(self):
+        return self._dstmonth != 0
+
+    @property
+    def _dst_base_offset(self):
+        return self._dst_base_offset_
+
+
+class tzwin(tzwinbase):
+    """
+    Time zone object created from the zone info in the Windows registry
+
+    These are similar to :py:class:`dateutil.tz.tzrange` objects in that
+    the time zone data is provided in the format of a single offset rule
+    for either 0 or 2 time zone transitions per year.
+
+    :param: name
+        The name of a Windows time zone key, e.g. "Eastern Standard Time".
+        The full list of keys can be retrieved with :func:`tzwin.list`.
+    """
+
+    def __init__(self, name):
+        self._name = name
+
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
+            with winreg.OpenKey(handle, tzkeyname) as tzkey:
+                keydict = valuestodict(tzkey)
+
+        self._std_abbr = keydict["Std"]
+        self._dst_abbr = keydict["Dlt"]
+
+        self._display = keydict["Display"]
+
+        # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
+        tup = struct.unpack("=3l16h", keydict["TZI"])
+        stdoffset = -tup[0]-tup[1]          # Bias + StandardBias * -1
+        dstoffset = stdoffset-tup[2]        # + DaylightBias * -1
+        self._std_offset = datetime.timedelta(minutes=stdoffset)
+        self._dst_offset = datetime.timedelta(minutes=dstoffset)
+
+        # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
+        # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
+        (self._stdmonth,
+         self._stddayofweek,   # Sunday = 0
+         self._stdweeknumber,  # Last = 5
+         self._stdhour,
+         self._stdminute) = tup[4:9]
+
+        (self._dstmonth,
+         self._dstdayofweek,   # Sunday = 0
+         self._dstweeknumber,  # Last = 5
+         self._dsthour,
+         self._dstminute) = tup[12:17]
+
+        self._dst_base_offset_ = self._dst_offset - self._std_offset
+        self.hasdst = self._get_hasdst()
+
+    def __repr__(self):
+        return "tzwin(%s)" % repr(self._name)
+
+    def __reduce__(self):
+        return (self.__class__, (self._name,))
+
+
+class tzwinlocal(tzwinbase):
+    """
+    Class representing the local time zone information in the Windows registry
+
+    While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
+    module) to retrieve time zone information, ``tzwinlocal`` retrieves the
+    rules directly from the Windows registry and creates an object like
+    :class:`dateutil.tz.tzwin`.
+
+    Because Windows does not have an equivalent of :func:`time.tzset`, on
+    Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
+    time zone settings *at the time that the process was started*, meaning
+    changes to the machine's time zone settings during the run of a program
+    on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
+    Because ``tzwinlocal`` reads the registry directly, it is unaffected by
+    this issue.
+    """
+    def __init__(self):
+        with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
+            with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
+                keydict = valuestodict(tzlocalkey)
+
+            self._std_abbr = keydict["StandardName"]
+            self._dst_abbr = keydict["DaylightName"]
+
+            try:
+                tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
+                                                          sn=self._std_abbr)
+                with winreg.OpenKey(handle, tzkeyname) as tzkey:
+                    _keydict = valuestodict(tzkey)
+                    self._display = _keydict["Display"]
+            except OSError:
+                self._display = None
+
+        stdoffset = -keydict["Bias"]-keydict["StandardBias"]
+        dstoffset = stdoffset-keydict["DaylightBias"]
+
+        self._std_offset = datetime.timedelta(minutes=stdoffset)
+        self._dst_offset = datetime.timedelta(minutes=dstoffset)
+
+        # For reasons unclear, in this particular key, the day of week has been
+        # moved to the END of the SYSTEMTIME structure.
+        tup = struct.unpack("=8h", keydict["StandardStart"])
+
+        (self._stdmonth,
+         self._stdweeknumber,  # Last = 5
+         self._stdhour,
+         self._stdminute) = tup[1:5]
+
+        self._stddayofweek = tup[7]
+
+        tup = struct.unpack("=8h", keydict["DaylightStart"])
+
+        (self._dstmonth,
+         self._dstweeknumber,  # Last = 5
+         self._dsthour,
+         self._dstminute) = tup[1:5]
+
+        self._dstdayofweek = tup[7]
+
+        self._dst_base_offset_ = self._dst_offset - self._std_offset
+        self.hasdst = self._get_hasdst()
+
+    def __repr__(self):
+        return "tzwinlocal()"
+
+    def __str__(self):
+        # str will return the standard name, not the daylight name.
+        return "tzwinlocal(%s)" % repr(self._std_abbr)
+
+    def __reduce__(self):
+        return (self.__class__, ())
+
+
+def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
+    """ dayofweek == 0 means Sunday, whichweek 5 means last instance """
+    first = datetime.datetime(year, month, 1, hour, minute)
+
+    # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
+    # Because 7 % 7 = 0
+    weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
+    wd = weekdayone + ((whichweek - 1) * ONEWEEK)
+    if (wd.month != month):
+        wd -= ONEWEEK
+
+    return wd
+
+
+def valuestodict(key):
+    """Convert a registry key's values to a dictionary."""
+    dout = {}
+    size = winreg.QueryInfoKey(key)[1]
+    tz_res = None
+
+    for i in range(size):
+        key_name, value, dtype = winreg.EnumValue(key, i)
+        if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
+            # If it's a DWORD (32-bit integer), it's stored as unsigned - convert
+            # that to a proper signed integer
+            if value & (1 << 31):
+                value = value - (1 << 32)
+        elif dtype == winreg.REG_SZ:
+            # If it's a reference to the tzres DLL, load the actual string
+            if value.startswith('@tzres'):
+                tz_res = tz_res or tzres()
+                value = tz_res.name_from_string(value)
+
+            value = value.rstrip('\x00')    # Remove trailing nulls
+
+        dout[key_name] = value
+
+    return dout

+ 2 - 0
venv/lib/python3.8/site-packages/dateutil/tzwin.py

@@ -0,0 +1,2 @@
+# tzwin has moved to dateutil.tz.win
+from .tz.win import *

+ 71 - 0
venv/lib/python3.8/site-packages/dateutil/utils.py

@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+"""
+This module offers general convenience and utility functions for dealing with
+datetimes.
+
+.. versionadded:: 2.7.0
+"""
+from __future__ import unicode_literals
+
+from datetime import datetime, time
+
+
+def today(tzinfo=None):
+    """
+    Returns a :py:class:`datetime` representing the current day at midnight
+
+    :param tzinfo:
+        The time zone to attach (also used to determine the current day).
+
+    :return:
+        A :py:class:`datetime.datetime` object representing the current day
+        at midnight.
+    """
+
+    dt = datetime.now(tzinfo)
+    return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
+
+
+def default_tzinfo(dt, tzinfo):
+    """
+    Sets the ``tzinfo`` parameter on naive datetimes only
+
+    This is useful for example when you are provided a datetime that may have
+    either an implicit or explicit time zone, such as when parsing a time zone
+    string.
+
+    .. doctest::
+
+        >>> from dateutil.tz import tzoffset
+        >>> from dateutil.parser import parse
+        >>> from dateutil.utils import default_tzinfo
+        >>> dflt_tz = tzoffset("EST", -18000)
+        >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
+        2014-01-01 12:30:00+00:00
+        >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
+        2014-01-01 12:30:00-05:00
+
+    :param dt:
+        The datetime on which to replace the time zone
+
+    :param tzinfo:
+        The :py:class:`datetime.tzinfo` subclass instance to assign to
+        ``dt`` if (and only if) it is naive.
+
+    :return:
+        Returns an aware :py:class:`datetime.datetime`.
+    """
+    if dt.tzinfo is not None:
+        return dt
+    else:
+        return dt.replace(tzinfo=tzinfo)
+
+
+def within_delta(dt1, dt2, delta):
+    """
+    Useful for comparing two datetimes that may a negilible difference
+    to be considered equal.
+    """
+    delta = abs(delta)
+    difference = dt1 - dt2
+    return -delta <= difference <= delta

+ 167 - 0
venv/lib/python3.8/site-packages/dateutil/zoneinfo/__init__.py

@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+import warnings
+import json
+
+from tarfile import TarFile
+from pkgutil import get_data
+from io import BytesIO
+
+from dateutil.tz import tzfile as _tzfile
+
+__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
+
+ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
+METADATA_FN = 'METADATA'
+
+
+class tzfile(_tzfile):
+    def __reduce__(self):
+        return (gettz, (self._filename,))
+
+
+def getzoneinfofile_stream():
+    try:
+        return BytesIO(get_data(__name__, ZONEFILENAME))
+    except IOError as e:  # TODO  switch to FileNotFoundError?
+        warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
+        return None
+
+
+class ZoneInfoFile(object):
+    def __init__(self, zonefile_stream=None):
+        if zonefile_stream is not None:
+            with TarFile.open(fileobj=zonefile_stream) as tf:
+                self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
+                              for zf in tf.getmembers()
+                              if zf.isfile() and zf.name != METADATA_FN}
+                # deal with links: They'll point to their parent object. Less
+                # waste of memory
+                links = {zl.name: self.zones[zl.linkname]
+                         for zl in tf.getmembers() if
+                         zl.islnk() or zl.issym()}
+                self.zones.update(links)
+                try:
+                    metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
+                    metadata_str = metadata_json.read().decode('UTF-8')
+                    self.metadata = json.loads(metadata_str)
+                except KeyError:
+                    # no metadata in tar file
+                    self.metadata = None
+        else:
+            self.zones = {}
+            self.metadata = None
+
+    def get(self, name, default=None):
+        """
+        Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
+        for retrieving zones from the zone dictionary.
+
+        :param name:
+            The name of the zone to retrieve. (Generally IANA zone names)
+
+        :param default:
+            The value to return in the event of a missing key.
+
+        .. versionadded:: 2.6.0
+
+        """
+        return self.zones.get(name, default)
+
+
+# The current API has gettz as a module function, although in fact it taps into
+# a stateful class. So as a workaround for now, without changing the API, we
+# will create a new "global" class instance the first time a user requests a
+# timezone. Ugly, but adheres to the api.
+#
+# TODO: Remove after deprecation period.
+_CLASS_ZONE_INSTANCE = []
+
+
+def get_zonefile_instance(new_instance=False):
+    """
+    This is a convenience function which provides a :class:`ZoneInfoFile`
+    instance using the data provided by the ``dateutil`` package. By default, it
+    caches a single instance of the ZoneInfoFile object and returns that.
+
+    :param new_instance:
+        If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
+        used as the cached instance for the next call. Otherwise, new instances
+        are created only as necessary.
+
+    :return:
+        Returns a :class:`ZoneInfoFile` object.
+
+    .. versionadded:: 2.6
+    """
+    if new_instance:
+        zif = None
+    else:
+        zif = getattr(get_zonefile_instance, '_cached_instance', None)
+
+    if zif is None:
+        zif = ZoneInfoFile(getzoneinfofile_stream())
+
+        get_zonefile_instance._cached_instance = zif
+
+    return zif
+
+
+def gettz(name):
+    """
+    This retrieves a time zone from the local zoneinfo tarball that is packaged
+    with dateutil.
+
+    :param name:
+        An IANA-style time zone name, as found in the zoneinfo file.
+
+    :return:
+        Returns a :class:`dateutil.tz.tzfile` time zone object.
+
+    .. warning::
+        It is generally inadvisable to use this function, and it is only
+        provided for API compatibility with earlier versions. This is *not*
+        equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
+        time zone based on the inputs, favoring system zoneinfo. This is ONLY
+        for accessing the dateutil-specific zoneinfo (which may be out of
+        date compared to the system zoneinfo).
+
+    .. deprecated:: 2.6
+        If you need to use a specific zoneinfofile over the system zoneinfo,
+        instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
+        :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
+
+        Use :func:`get_zonefile_instance` to retrieve an instance of the
+        dateutil-provided zoneinfo.
+    """
+    warnings.warn("zoneinfo.gettz() will be removed in future versions, "
+                  "to use the dateutil-provided zoneinfo files, instantiate a "
+                  "ZoneInfoFile object and use ZoneInfoFile.zones.get() "
+                  "instead. See the documentation for details.",
+                  DeprecationWarning)
+
+    if len(_CLASS_ZONE_INSTANCE) == 0:
+        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
+    return _CLASS_ZONE_INSTANCE[0].zones.get(name)
+
+
+def gettz_db_metadata():
+    """ Get the zonefile metadata
+
+    See `zonefile_metadata`_
+
+    :returns:
+        A dictionary with the database metadata
+
+    .. deprecated:: 2.6
+        See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
+        query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
+    """
+    warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
+                  "versions, to use the dateutil-provided zoneinfo files, "
+                  "ZoneInfoFile object and query the 'metadata' attribute "
+                  "instead. See the documentation for details.",
+                  DeprecationWarning)
+
+    if len(_CLASS_ZONE_INSTANCE) == 0:
+        _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
+    return _CLASS_ZONE_INSTANCE[0].metadata

BIN
venv/lib/python3.8/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz


+ 53 - 0
venv/lib/python3.8/site-packages/dateutil/zoneinfo/rebuild.py

@@ -0,0 +1,53 @@
+import logging
+import os
+import tempfile
+import shutil
+import json
+from subprocess import check_call
+from tarfile import TarFile
+
+from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
+
+
+def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
+    """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
+
+    filename is the timezone tarball from ``ftp.iana.org/tz``.
+
+    """
+    tmpdir = tempfile.mkdtemp()
+    zonedir = os.path.join(tmpdir, "zoneinfo")
+    moduledir = os.path.dirname(__file__)
+    try:
+        with TarFile.open(filename) as tf:
+            for name in zonegroups:
+                tf.extract(name, tmpdir)
+            filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
+            try:
+                check_call(["zic", "-d", zonedir] + filepaths)
+            except OSError as e:
+                _print_on_nosuchfile(e)
+                raise
+        # write metadata file
+        with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
+            json.dump(metadata, f, indent=4, sort_keys=True)
+        target = os.path.join(moduledir, ZONEFILENAME)
+        with TarFile.open(target, "w:%s" % format) as tf:
+            for entry in os.listdir(zonedir):
+                entrypath = os.path.join(zonedir, entry)
+                tf.add(entrypath, entry)
+    finally:
+        shutil.rmtree(tmpdir)
+
+
+def _print_on_nosuchfile(e):
+    """Print helpful troubleshooting message
+
+    e is an exception raised by subprocess.check_call()
+
+    """
+    if e.errno == 2:
+        logging.error(
+            "Could not find zic. Perhaps you need to install "
+            "libc-bin or some other package that provides it, "
+            "or it's not in your PATH?")

+ 1 - 0
venv/lib/python3.8/site-packages/easy-install.pth

@@ -0,0 +1 @@
+./pygame-1.9.6-py3.8-linux-x86_64.egg

+ 5 - 0
venv/lib/python3.8/site-packages/easy_install.py

@@ -0,0 +1,5 @@
+"""Run the EasyInstall command"""
+
+if __name__ == '__main__':
+    from setuptools.command.easy_install import main
+    main()

+ 1 - 0
venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 41 - 0
venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/METADATA

@@ -0,0 +1,41 @@
+Metadata-Version: 2.1
+Name: kiwisolver
+Version: 1.2.0
+Summary: A fast implementation of the Cassowary constraint solver
+Home-page: https://github.com/nucleic/kiwi
+Author: The Nucleic Development Team
+Author-email: sccolbert@gmail.com
+License: BSD
+Platform: UNKNOWN
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Requires-Python: >=3.6
+
+Welcome to Kiwi
+===============
+
+.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
+    :target: https://travis-ci.org/nucleic/kiwi
+.. image:: https://codecov.io/gh/nucleic/kiwi/branch/master/graph/badge.svg
+  :target: https://codecov.io/gh/nucleic/kiwi
+.. image:: https://readthedocs.org/projects/kiwisolver/badge/?version=latest
+    :target: https://kiwisolver.readthedocs.io/en/latest/?badge=latest
+    :alt: Documentation Status
+
+Kiwi is an efficient C++ implementation of the Cassowary constraint solving
+algorithm. Kiwi is an implementation of the algorithm based on the seminal
+Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
+has been designed from the ground up to be lightweight and fast. Kiwi ranges
+from 10x to 500x faster than the original Cassowary solver with typical use
+cases gaining a 40x improvement. Memory savings are consistently > 5x.
+
+In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
+
+The version 1.1.0 of the Python bindings will be the last one to support
+Python 2, moving forward support will be limited to Python 3.5+.
+
+

+ 6 - 0
venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/RECORD

@@ -0,0 +1,6 @@
+kiwisolver-1.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+kiwisolver-1.2.0.dist-info/METADATA,sha256=GJrhSqJ37gKKWd9lqPultG3mcP013xKeHLD7QyF5z4M,1718
+kiwisolver-1.2.0.dist-info/RECORD,,
+kiwisolver-1.2.0.dist-info/WHEEL,sha256=VEyGcIFAmk_1KbI6gaZGw_mMiT-pdGweASQLX-DzYaY,108
+kiwisolver-1.2.0.dist-info/top_level.txt,sha256=xqwWj7oSHlpIjcw2QMJb8puTFPdjDBO78AZp9gjTh9c,11
+kiwisolver.cpython-38-x86_64-linux-gnu.so,sha256=M6rlCMjCLuAQQkwvZn0yHSInz15TxuUvZwzjN9Oscr4,254056

+ 5 - 0
venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.31.1)
+Root-Is-Purelib: false
+Tag: cp38-cp38-manylinux1_x86_64
+

+ 1 - 0
venv/lib/python3.8/site-packages/kiwisolver-1.2.0.dist-info/top_level.txt

@@ -0,0 +1 @@
+kiwisolver

BIN
venv/lib/python3.8/site-packages/kiwisolver.cpython-38-x86_64-linux-gnu.so


+ 1 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1-py3.8-nspkg.pth

@@ -0,0 +1 @@
+import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('mpl_toolkits',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('mpl_toolkits', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('mpl_toolkits', [os.path.dirname(p)])));m = m or sys.modules.setdefault('mpl_toolkits', types.ModuleType('mpl_toolkits'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)

+ 1 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 127 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/METADATA

@@ -0,0 +1,127 @@
+Metadata-Version: 2.1
+Name: matplotlib
+Version: 3.2.1
+Summary: Python plotting package
+Home-page: https://matplotlib.org
+Author: John D. Hunter, Michael Droettboom
+Author-email: matplotlib-users@python.org
+License: PSF
+Download-URL: https://matplotlib.org/users/installing.html
+Project-URL: Documentation, https://matplotlib.org
+Project-URL: Source Code, https://github.com/matplotlib/matplotlib
+Project-URL: Bug Tracker, https://github.com/matplotlib/matplotlib/issues
+Project-URL: Forum, https://discourse.matplotlib.org/
+Project-URL: Donate, https://numfocus.org/donate-to-matplotlib
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Framework :: Matplotlib
+Classifier: Intended Audience :: Science/Research
+Classifier: Intended Audience :: Education
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Scientific/Engineering :: Visualization
+Requires-Python: >=3.6
+Description-Content-Type: text/x-rst
+Requires-Dist: cycler (>=0.10)
+Requires-Dist: kiwisolver (>=1.0.1)
+Requires-Dist: numpy (>=1.11)
+Requires-Dist: pyparsing (!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1)
+Requires-Dist: python-dateutil (>=2.1)
+
+|Travis|_ |AzurePipelines|_ |AppVeyor|_ |Codecov|_ |LGTM|_ |PyPi|_ |Gitter|_ |NUMFocus|_ |GitTutorial|_
+
+
+.. |Travis| image:: https://travis-ci.org/matplotlib/matplotlib.svg?branch=master
+.. _Travis: https://travis-ci.org/matplotlib/matplotlib
+
+.. |AzurePipelines| image:: https://dev.azure.com/matplotlib/matplotlib/_apis/build/status/matplotlib.matplotlib?branchName=master
+.. _AzurePipelines: https://dev.azure.com/matplotlib/matplotlib/_build/latest?definitionId=1&branchName=master
+
+.. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/github/matplotlib/matplotlib?branch=master&svg=true
+.. _AppVeyor: https://ci.appveyor.com/project/matplotlib/matplotlib
+
+.. |Codecov| image:: https://codecov.io/github/matplotlib/matplotlib/badge.svg?branch=master&service=github
+.. _Codecov: https://codecov.io/github/matplotlib/matplotlib?branch=master
+
+.. |LGTM| image:: https://img.shields.io/lgtm/grade/python/g/matplotlib/matplotlib.svg?logo=lgtm&logoWidth=18
+.. _LGTM: https://lgtm.com/projects/g/matplotlib/matplotlib
+
+.. |PyPi| image:: https://badge.fury.io/py/matplotlib.svg
+.. _PyPi: https://badge.fury.io/py/matplotlib
+
+.. |Gitter| image:: https://badges.gitter.im/matplotlib/matplotlib.png
+.. _Gitter: https://gitter.im/matplotlib/matplotlib
+
+.. |NUMFocus| image:: https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A
+.. _NUMFocus: https://www.numfocus.org
+
+.. |GitTutorial| image:: https://img.shields.io/badge/PR-Welcome-%23FF8300.svg?
+.. _GitTutorial: https://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project
+
+.. image:: https://matplotlib.org/_static/logo2.svg
+
+
+Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python.
+
+Check out our `home page <https://matplotlib.org/>`_ for more information.
+
+.. image:: https://matplotlib.org/_static/readme_preview.png
+
+Matplotlib produces publication-quality figures in a variety of hardcopy formats
+and interactive environments across platforms. Matplotlib can be used in Python scripts,
+the Python and IPython shell, web application servers, and various
+graphical user interface toolkits.
+
+
+Install
+=======
+
+For installation instructions and requirements, see `INSTALL.rst <INSTALL.rst>`_  or the
+`install <https://matplotlib.org/users/installing.html>`_ documentation.
+
+Test
+====
+
+After installation, launch the test suite::
+
+  python -m pytest
+
+Read the `testing guide <https://matplotlib.org/devel/testing.html>`_ for more information and alternatives.
+
+Contribute
+==========
+You've discovered a bug or something else you want to change - excellent!
+
+You've worked out a way to fix it – even better!
+
+You want to tell us about it – best of all!
+
+Start at the `contributing guide <https://matplotlib.org/devdocs/devel/contributing.html>`_!
+
+Contact
+=======
+
+`Discourse <https://discourse.matplotlib.org/>`_ is the discussion forum for general questions and discussions and our recommended starting point.
+
+Our active mailing lists (which are mirrored on Discourse) are:
+
+* `Users <https://mail.python.org/mailman/listinfo/matplotlib-users>`_ mailing list: matplotlib-users@python.org
+* `Announcement  <https://mail.python.org/mailman/listinfo/matplotlib-announce>`_ mailing list: matplotlib-announce@python.org
+* `Development <https://mail.python.org/mailman/listinfo/matplotlib-devel>`_ mailing list: matplotlib-devel@python.org
+
+Gitter_ is for coordinating development and asking questions directly related
+to contributing to matplotlib.
+
+
+Citing Matplotlib
+=================
+If Matplotlib contributes to a project that leads to publication, please
+acknowledge this by citing Matplotlib.
+
+`A ready-made citation entry <https://matplotlib.org/citing.html>`_ is available.
+
+

+ 892 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/RECORD

@@ -0,0 +1,892 @@
+__pycache__/pylab.cpython-38.pyc,,
+matplotlib-3.2.1-py3.8-nspkg.pth,sha256=FgO_3ug071EXEKT8mgOPBUhyrswPtPCYjOpUCyau7UU,569
+matplotlib-3.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+matplotlib-3.2.1.dist-info/METADATA,sha256=F1oGQTHWb-ltDWNhlYUpDR9UvF5wAQ7l9pyVaRKk_1M,5170
+matplotlib-3.2.1.dist-info/RECORD,,
+matplotlib-3.2.1.dist-info/WHEEL,sha256=VEyGcIFAmk_1KbI6gaZGw_mMiT-pdGweASQLX-DzYaY,108
+matplotlib-3.2.1.dist-info/namespace_packages.txt,sha256=A2PHFg9NKYOU4pEQ1h97U0Qd-rB-65W34XqC-56ZN9g,13
+matplotlib-3.2.1.dist-info/top_level.txt,sha256=9tEw2ni8DdgX8CceoYHqSH1s50vrJ9SDfgtLIG8e3Y4,30
+matplotlib/.libs/libpng16-cfdb1654.so.16.21.0,sha256=Fo8LBDWTuCclLkpSng_KP5pI7wcQtuXA9opT1FFkXl0,275648
+matplotlib/.libs/libz-a147dcb0.so.1.2.3,sha256=VwXH3AM7bnoa793tKDw_H0pW-VZos08-FEtM_g_VWVM,87848
+matplotlib/__init__.py,sha256=FLEIOOmlbJDBzqg4aq8W6w0wSpQF9tVwGu0VW8EwqsA,54615
+matplotlib/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/__pycache__/_animation_data.cpython-38.pyc,,
+matplotlib/__pycache__/_cm.cpython-38.pyc,,
+matplotlib/__pycache__/_cm_listed.cpython-38.pyc,,
+matplotlib/__pycache__/_color_data.cpython-38.pyc,,
+matplotlib/__pycache__/_constrained_layout.cpython-38.pyc,,
+matplotlib/__pycache__/_layoutbox.cpython-38.pyc,,
+matplotlib/__pycache__/_mathtext_data.cpython-38.pyc,,
+matplotlib/__pycache__/_pylab_helpers.cpython-38.pyc,,
+matplotlib/__pycache__/_text_layout.cpython-38.pyc,,
+matplotlib/__pycache__/_version.cpython-38.pyc,,
+matplotlib/__pycache__/afm.cpython-38.pyc,,
+matplotlib/__pycache__/animation.cpython-38.pyc,,
+matplotlib/__pycache__/artist.cpython-38.pyc,,
+matplotlib/__pycache__/axis.cpython-38.pyc,,
+matplotlib/__pycache__/backend_bases.cpython-38.pyc,,
+matplotlib/__pycache__/backend_managers.cpython-38.pyc,,
+matplotlib/__pycache__/backend_tools.cpython-38.pyc,,
+matplotlib/__pycache__/bezier.cpython-38.pyc,,
+matplotlib/__pycache__/blocking_input.cpython-38.pyc,,
+matplotlib/__pycache__/category.cpython-38.pyc,,
+matplotlib/__pycache__/cm.cpython-38.pyc,,
+matplotlib/__pycache__/collections.cpython-38.pyc,,
+matplotlib/__pycache__/colorbar.cpython-38.pyc,,
+matplotlib/__pycache__/colors.cpython-38.pyc,,
+matplotlib/__pycache__/container.cpython-38.pyc,,
+matplotlib/__pycache__/contour.cpython-38.pyc,,
+matplotlib/__pycache__/dates.cpython-38.pyc,,
+matplotlib/__pycache__/docstring.cpython-38.pyc,,
+matplotlib/__pycache__/dviread.cpython-38.pyc,,
+matplotlib/__pycache__/figure.cpython-38.pyc,,
+matplotlib/__pycache__/font_manager.cpython-38.pyc,,
+matplotlib/__pycache__/fontconfig_pattern.cpython-38.pyc,,
+matplotlib/__pycache__/gridspec.cpython-38.pyc,,
+matplotlib/__pycache__/hatch.cpython-38.pyc,,
+matplotlib/__pycache__/image.cpython-38.pyc,,
+matplotlib/__pycache__/legend.cpython-38.pyc,,
+matplotlib/__pycache__/legend_handler.cpython-38.pyc,,
+matplotlib/__pycache__/lines.cpython-38.pyc,,
+matplotlib/__pycache__/markers.cpython-38.pyc,,
+matplotlib/__pycache__/mathtext.cpython-38.pyc,,
+matplotlib/__pycache__/mlab.cpython-38.pyc,,
+matplotlib/__pycache__/offsetbox.cpython-38.pyc,,
+matplotlib/__pycache__/patches.cpython-38.pyc,,
+matplotlib/__pycache__/path.cpython-38.pyc,,
+matplotlib/__pycache__/patheffects.cpython-38.pyc,,
+matplotlib/__pycache__/pylab.cpython-38.pyc,,
+matplotlib/__pycache__/pyplot.cpython-38.pyc,,
+matplotlib/__pycache__/quiver.cpython-38.pyc,,
+matplotlib/__pycache__/rcsetup.cpython-38.pyc,,
+matplotlib/__pycache__/sankey.cpython-38.pyc,,
+matplotlib/__pycache__/scale.cpython-38.pyc,,
+matplotlib/__pycache__/spines.cpython-38.pyc,,
+matplotlib/__pycache__/stackplot.cpython-38.pyc,,
+matplotlib/__pycache__/streamplot.cpython-38.pyc,,
+matplotlib/__pycache__/table.cpython-38.pyc,,
+matplotlib/__pycache__/texmanager.cpython-38.pyc,,
+matplotlib/__pycache__/text.cpython-38.pyc,,
+matplotlib/__pycache__/textpath.cpython-38.pyc,,
+matplotlib/__pycache__/ticker.cpython-38.pyc,,
+matplotlib/__pycache__/tight_bbox.cpython-38.pyc,,
+matplotlib/__pycache__/tight_layout.cpython-38.pyc,,
+matplotlib/__pycache__/transforms.cpython-38.pyc,,
+matplotlib/__pycache__/type1font.cpython-38.pyc,,
+matplotlib/__pycache__/units.cpython-38.pyc,,
+matplotlib/__pycache__/widgets.cpython-38.pyc,,
+matplotlib/_animation_data.py,sha256=yClmMx6K-y6pjG3FdHancRyRhyneFuBEbQZ_lhezVys,7499
+matplotlib/_cm.py,sha256=nZCQdTWsPc5aJ-n08l2g293Wwg0kSWIgcfStzv9Dtxg,66643
+matplotlib/_cm_listed.py,sha256=9aMZ1uoTkxeDKlXfUNmY99HEtAXsW_JcSpwYaBs0kHs,98165
+matplotlib/_color_data.py,sha256=K2HSKblmuh-X_1ZZ9TcXcP7iKHaGC4mC_ScWqX_tdXE,34947
+matplotlib/_constrained_layout.py,sha256=eafuhM2rw5SL_ilqf0ImxQwtmFX_pR_boeKUrjTHvB0,29678
+matplotlib/_contour.cpython-38-x86_64-linux-gnu.so,sha256=J8lraYcOxIbZ6hAIyK0zvSYKyRunSzUdFyZSmcqF004,95144
+matplotlib/_image.cpython-38-x86_64-linux-gnu.so,sha256=jiNef3vzvaDg8wpvmd-fASybETFlxpgOlQiSYzofU_Q,239232
+matplotlib/_layoutbox.py,sha256=LG5KEmNm5apZa1putqzafEpX9w4lLwkoOPxg4owiO2Y,23872
+matplotlib/_mathtext_data.py,sha256=CmKFRW6mXCJqgZSQaiNOSG_VUn9WiSx5Hrg-4qKIn14,89371
+matplotlib/_path.cpython-38-x86_64-linux-gnu.so,sha256=Eb0DMGN_CF3SuY27zCnwjyranrGxg60lR8UYHuj6nSQ,190216
+matplotlib/_png.cpython-38-x86_64-linux-gnu.so,sha256=wL4br11noZizM-qrCC6H39HHmIFJkNibGmttbjh7Jdo,35552
+matplotlib/_pylab_helpers.py,sha256=RscVbvWEk6QyznzVvX-lSHkWCUOo9Lik7D7j4xelQJ4,3445
+matplotlib/_qhull.cpython-38-x86_64-linux-gnu.so,sha256=byRIOIOe-V5tqwnMCI7i-3lhqlNAC6VC6MP78x-7EpM,382672
+matplotlib/_text_layout.py,sha256=88DxzfAOPzpRjpu0OwLaRl6eOVJ5Var8ZxrDyhAQ7C8,1036
+matplotlib/_tri.cpython-38-x86_64-linux-gnu.so,sha256=7wzrMnAFHdKA3-9Eg3A-3x8E-_jDdkm4bJRZdHxiFBk,125960
+matplotlib/_version.py,sha256=etxeX_ByADCJBN53WhU2IEezakT1gixcO41qmDlMwfY,471
+matplotlib/afm.py,sha256=Y2FmLqutIXI__QiAbMNpFclleQrpGf1xsqBgjyCZir0,16642
+matplotlib/animation.py,sha256=N0HzAqLF1fscM2OIkER3gv4zsqUXWpxA8BmE_3Jqi5k,68066
+matplotlib/artist.py,sha256=2Qd4EUb9n4FAizD1fdb48diR7yRivgwvY8JESg4r4mE,53229
+matplotlib/axes/__init__.py,sha256=npQuBvs_xEBEGUP2-BBZzCrelsAQYgB1U96kSZTSWIs,46
+matplotlib/axes/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/axes/__pycache__/_axes.cpython-38.pyc,,
+matplotlib/axes/__pycache__/_base.cpython-38.pyc,,
+matplotlib/axes/__pycache__/_secondary_axes.cpython-38.pyc,,
+matplotlib/axes/__pycache__/_subplots.cpython-38.pyc,,
+matplotlib/axes/_axes.py,sha256=dSaGhVBB00lKf1AF9mCsaFcleTU8qKlSkVNP2ET_CPU,312519
+matplotlib/axes/_base.py,sha256=x89D4ElapqkI9lQ0oTAwTGcKPm-m-J26Fg-A0R3Ys1M,159726
+matplotlib/axes/_secondary_axes.py,sha256=plMlrvrznuuBSRakuznyCyr_ZNnSiuc_3J-YD3Hg8e8,14352
+matplotlib/axes/_subplots.py,sha256=rkT6vCCbCTItY_oAmlT-1rSPOoIerjScJC91I2YLTp0,10203
+matplotlib/axis.py,sha256=50zN9IvoaYGqq8t8zUiaaTtcUJeNjxdAnmXioRlfNWc,90049
+matplotlib/backend_bases.py,sha256=vNzjru0RDGIlzyeT9vjmWv8ktEFaax0C7W1tXz1qR_w,117888
+matplotlib/backend_managers.py,sha256=HuHOXPFjVF1R6ttBLxlucsLcEsWlRXtbm_wr1OJer2Q,12819
+matplotlib/backend_tools.py,sha256=zVBxfyPoQx0_lQe1Su0o-zTycOQt-zKWTp3HEY-jTxI,36252
+matplotlib/backends/__init__.py,sha256=cKAeiC5g0Up9svoxT24paOcnKc4tb5cbfbF-AWxXLUE,1722
+matplotlib/backends/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/backends/__pycache__/_backend_pdf_ps.cpython-38.pyc,,
+matplotlib/backends/__pycache__/_backend_tk.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_agg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_cairo.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_gtk3.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_gtk3agg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_macosx.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_mixed.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_nbagg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_pdf.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_pgf.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_ps.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_qt4.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_qt4agg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_qt4cairo.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_qt5.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_qt5agg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_qt5cairo.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_svg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_template.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_tkagg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_tkcairo.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_webagg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_webagg_core.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_wx.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_wxagg.cpython-38.pyc,,
+matplotlib/backends/__pycache__/backend_wxcairo.cpython-38.pyc,,
+matplotlib/backends/__pycache__/qt_compat.cpython-38.pyc,,
+matplotlib/backends/_backend_agg.cpython-38-x86_64-linux-gnu.so,sha256=UvDwI9RPPoFGH_PODmYP6DqcgeGCiqEoloxbDWaY51Y,358224
+matplotlib/backends/_backend_pdf_ps.py,sha256=T8x_NWAo9_Wvnpw6HnsneRI9AyAxcp80TDe-z2eosbM,2709
+matplotlib/backends/_backend_tk.py,sha256=k1eZjNeVC0rDMEKlOqevga-PFTERv9mcwUn_d_LZUyo,32021
+matplotlib/backends/_tkagg.cpython-38-x86_64-linux-gnu.so,sha256=r_YnlqbmC6XT7-Mm6AdmjNWa0mBoQLJd9xmbZow6HI0,27192
+matplotlib/backends/backend_agg.py,sha256=bjt43QEsQ-AeEeWXSBwRcbJsuDU7Dex-JsdbApekXzA,22347
+matplotlib/backends/backend_cairo.py,sha256=zJAQ5_OkKchNrItE8jN9CriAw4aQ0PP3qKqfINu3CSc,16562
+matplotlib/backends/backend_gtk3.py,sha256=ED_iXiCIXQlMJmh8YlJJcPkCxnm9Wly7kSod0UogtRc,33832
+matplotlib/backends/backend_gtk3agg.py,sha256=iKLBQ48c9Vh4krSxYeSDbHZ4G9wXoezk2VTIgNR4BWw,3072
+matplotlib/backends/backend_gtk3cairo.py,sha256=TDQUuYq5KCPvAU-MY2wBxpx2zAn9vOO_ieeE_7fdtS4,1591
+matplotlib/backends/backend_macosx.py,sha256=fALNUVEkZrqRrMHe5G_wy5i7FODvXXIum7RU2PXOiG4,5829
+matplotlib/backends/backend_mixed.py,sha256=R3-Z5AW7anq31csMjylsY4pSehsAsBLrxFD5YhZkZ6w,5280
+matplotlib/backends/backend_nbagg.py,sha256=xAv_OZfz6NtMrbAXWqwX9GCgTEsb5e5FpyAMHSIE-j4,8902
+matplotlib/backends/backend_pdf.py,sha256=sq_dTpmyOkYrtWdHQGKanZxRezXFmfeZNCMcObJR95A,96197
+matplotlib/backends/backend_pgf.py,sha256=R34xfvJyVm0k4IgjCHPU0bKeP02zJ0_Pr0hV6yaCVv8,43431
+matplotlib/backends/backend_ps.py,sha256=MWZmgx9nrlp9_u50gp_JZE2bEt2E3ixZxPqT9Wk5BqY,50575
+matplotlib/backends/backend_qt4.py,sha256=H9AsG4NAF27LdGUJDi1n5RwJhK1lI43sa9VXtUgW3K8,397
+matplotlib/backends/backend_qt4agg.py,sha256=BXMTBYmqyT_uvEpgSMWts5m2q1YK0vhPeybt0ZMVd0I,292
+matplotlib/backends/backend_qt4cairo.py,sha256=cvXKhk8CZvceDiEl9OXmumjJ6dZ3k0u93xy8w8i24gQ,229
+matplotlib/backends/backend_qt5.py,sha256=BjGTvVXytXhqRPpYNzoWaO03ULCfKdF3xG78e9nr0Sc,39256
+matplotlib/backends/backend_qt5agg.py,sha256=sF1oFyKNmOAdCGUChUbrKfmFmNo8QuD2SzuQQl4U0Mg,3560
+matplotlib/backends/backend_qt5cairo.py,sha256=YzXN1Ckr6JLS2r50LPxeypajdWccXaHoXC5QZ4VWrEY,1892
+matplotlib/backends/backend_svg.py,sha256=KGdJMf-cXIW82s0MC9-wOsmtEeo8P3np_uK06z2FGkI,43552
+matplotlib/backends/backend_template.py,sha256=SVPx4bl2n0eKgEolfJH7ljJnqlak0XhFE6Zr3yr6GU4,8407
+matplotlib/backends/backend_tkagg.py,sha256=WMslLWYmtxlmAaBH4tx4HjmRDWMKiSV91KHF9yeMRng,676
+matplotlib/backends/backend_tkcairo.py,sha256=dVCh7ZD_2OR0DBQ0N3icD8cDV1SeEzCsRja446wWhPw,1069
+matplotlib/backends/backend_webagg.py,sha256=NtP1VSSXQNvB_EBQi14No7flt1C64VwekiTG58tt7LU,10794
+matplotlib/backends/backend_webagg_core.py,sha256=u026hAuD_dwL0a2D7g--jzBr_KFS5UqJdFZDvIdlmII,17558
+matplotlib/backends/backend_wx.py,sha256=O606ybHYiOFCxpnhpZ_TYaHdULG53YuAZQDq70xjoms,66672
+matplotlib/backends/backend_wxagg.py,sha256=Jhb24f2W6e5yCqSi4F971hor_I8Epf_WMfutq3VoTYI,3027
+matplotlib/backends/backend_wxcairo.py,sha256=VC5TyJaX8TPLSgHv5ckAreoGrY_KiNRMQjVInMLlcFk,1843
+matplotlib/backends/qt_compat.py,sha256=Me6QS6xk4ASjOdBhr7RxvIp4Frzbdmu7mIXRO5uJlwU,6551
+matplotlib/backends/qt_editor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+matplotlib/backends/qt_editor/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/backends/qt_editor/__pycache__/_formlayout.cpython-38.pyc,,
+matplotlib/backends/qt_editor/__pycache__/figureoptions.cpython-38.pyc,,
+matplotlib/backends/qt_editor/__pycache__/formlayout.cpython-38.pyc,,
+matplotlib/backends/qt_editor/__pycache__/formsubplottool.cpython-38.pyc,,
+matplotlib/backends/qt_editor/_formlayout.py,sha256=aHC33AfwtVjly7sPjLTFrVABZqAD2RAloR6KCEAUv5A,20592
+matplotlib/backends/qt_editor/figureoptions.py,sha256=YySx6A_wZSACMEHbXYkoQ6wR0_6PPwON2c6YwejUeVs,9481
+matplotlib/backends/qt_editor/formlayout.py,sha256=ERfmFwpvhl168PWNTJ0SFhQmPuSrmjzFNOe_puUCoSE,177
+matplotlib/backends/qt_editor/formsubplottool.py,sha256=HiiXkwCotra_hI9JU208KOs8Q9JuGH1uAW3mV5l3Evg,1934
+matplotlib/backends/web_backend/all_figures.html,sha256=m20aQIhuI4GBdrgZg_j02zSVjAcTRUufPOMSe4i7ayc,1525
+matplotlib/backends/web_backend/css/boilerplate.css,sha256=qui16QXRnQFNJDbcMasfH6KtN9hLjv8883U9cJmsVCE,2310
+matplotlib/backends/web_backend/css/fbm.css,sha256=Us0osu_rK8EUAdp_GXrh89tN_hUNCN-r7N1T1NvmmwI,1473
+matplotlib/backends/web_backend/css/page.css,sha256=Djf6ZNMFaM6_hVaizSkDFoqk-jn81qgduwles4AroGk,1599
+matplotlib/backends/web_backend/ipython_inline_figure.html,sha256=mzi-yWg4fcO6PdtTBCfiNuvcv04T53lcRQi-8hphwuE,1305
+matplotlib/backends/web_backend/jquery-ui-1.12.1/AUTHORS.txt,sha256=W2Lh1mbGo3Owc0oXX9U1-TFVSZYaC72KvSRrrRp3UII,12660
+matplotlib/backends/web_backend/jquery-ui-1.12.1/LICENSE.txt,sha256=3jP7aViA0LB2FdS4b3jNQ3lpBpWa3l_f73CWiCeg23g,1817
+matplotlib/backends/web_backend/jquery-ui-1.12.1/external/jquery/jquery.js,sha256=Qw82-bXyGq6MydymqBxNPYTaUXXq7c8v3CwiYwLLNXU,293430
+matplotlib/backends/web_backend/jquery-ui-1.12.1/images/ui-icons_444444_256x240.png,sha256=6vfH7idHJ13abFPnMaENsaexX0-7RuG2nWuyBWvJ_YE,7006
+matplotlib/backends/web_backend/jquery-ui-1.12.1/images/ui-icons_555555_256x240.png,sha256=XQQFHf2dLXQDVUBPmKaD0ewP6y_KfXblM8Gm5c6S3S4,7074
+matplotlib/backends/web_backend/jquery-ui-1.12.1/images/ui-icons_777620_256x240.png,sha256=nb5KDQP-7W9l6yVgoKi0ukJkVF7o_THBdjo7IZ0DKNY,4676
+matplotlib/backends/web_backend/jquery-ui-1.12.1/images/ui-icons_777777_256x240.png,sha256=51snIR4W_PlHFRaAAbtwVco3bUb5KBELo9CCUjJFLlo,7013
+matplotlib/backends/web_backend/jquery-ui-1.12.1/images/ui-icons_cc0000_256x240.png,sha256=AokVddQ1jp7d4-QtlAV_jp-CqdZDdvce6GzvFJ0wU34,4632
+matplotlib/backends/web_backend/jquery-ui-1.12.1/images/ui-icons_ffffff_256x240.png,sha256=trBt7vK5JMw4NdY_SIPUeIJzSjPnGyEtkXpozt47jp0,6313
+matplotlib/backends/web_backend/jquery-ui-1.12.1/index.html,sha256=5g7_MLZlkh92FXWOR0q02My8knssXq20DXz-BkiYiP4,32588
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.css,sha256=p6xU9YulB7E2Ic62_PX-h59ayb3PBJ0WFTEQxq0EjHw,37326
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.js,sha256=T0Vest3yCU7pafRw9r-settMBX6JkKN06dqBnpQ8d30,520714
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.min.css,sha256=rByPlHULObEjJ6XQxW_flG2r-22R5dKiAoef-aXWfik,32076
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.min.js,sha256=KM512VNnjElC30ehFwehXjx1YCHPiQkOPmqnrWtpccM,253669
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.structure.css,sha256=E1uqV-d412nbSI-oqDMIQsTSttP-FS7Bxwc7mQdQYOo,18705
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.structure.min.css,sha256=rxais37anKUnpL5QzSYte-JnIsmkGmLG-ZhKSkZkwVM,15548
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.theme.css,sha256=mEMD30TTg-vIEGUmHHgcgSOgm0FBfLipyQ97Jr0TTH8,18671
+matplotlib/backends/web_backend/jquery-ui-1.12.1/jquery-ui.theme.min.css,sha256=AjyoyaRtnGVTywKH_Isxxu5PXI0s4CcE0BzPAX83Ppc,13849
+matplotlib/backends/web_backend/jquery-ui-1.12.1/package.json,sha256=kjEW8xMYuqRSwEE58KqTDNLgkx_6YL7tb1M9vlMK98w,1847
+matplotlib/backends/web_backend/js/mpl.js,sha256=xrBOaet7K2P2iq7wgIexceDdc9T_WFb4TPJXDX55JuU,16991
+matplotlib/backends/web_backend/js/mpl_tornado.js,sha256=lSxC7-yqF1GYY-6SheaHanx6SujMdcG7Vx2_3qbi-9Q,272
+matplotlib/backends/web_backend/js/nbagg_mpl.js,sha256=nqIF0zFBQGpOo5Tmq2uRkyFJDeali66PWQDSYySgpnQ,7428
+matplotlib/backends/web_backend/nbagg_uat.ipynb,sha256=y1N8hQzBJ05rJ2hZla2_Mw6tOUfNP1UHKo636W1e098,15933
+matplotlib/backends/web_backend/single_figure.html,sha256=-iFrlIsaY1rOK9bNiDxcX8fdc0WP7DXXq-MEuLYfOvM,1216
+matplotlib/bezier.py,sha256=Fqb2gqRnmS2wYjEZVFcdSngRxh5g_hFnxX82RFpimec,17271
+matplotlib/blocking_input.py,sha256=soMcLeXT2mKxkH5fyQqYEOP_Dd-TUjoA1_L7imb2B2Q,11105
+matplotlib/category.py,sha256=_iJbNlZzi3kqPtz_bhEBCj8q6RsBp5Urd3yNS81dOQg,7131
+matplotlib/cbook/__init__.py,sha256=k7_Gdpbg26mrQlfQJr3eTnRGvXInhfuEjWeBrY5-IlY,70426
+matplotlib/cbook/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/cbook/__pycache__/deprecation.cpython-38.pyc,,
+matplotlib/cbook/deprecation.py,sha256=E8xSJYv-br0VswU-LNQFTU0VlurdH6v2rNOMCvOfs2I,15080
+matplotlib/cm.py,sha256=qo1ZhJvKa7dCfALcj1X6rsy7R60pbJht7zK6pi_9EcA,12623
+matplotlib/collections.py,sha256=n_Mru2nfEhqvAGHAD3g_JhWAoxvtK5MjYFLfztvSK3s,74328
+matplotlib/colorbar.py,sha256=gaI17iRKX4NntEaLROYsg4q1fXUboda2L0w7PUaWSKg,63074
+matplotlib/colors.py,sha256=auWiOGPMdI5twHSoVVI0AyEe7O9LJfn3fKetp_8yMzA,75537
+matplotlib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+matplotlib/compat/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/container.py,sha256=yAq9s8ylaQxkt2N-qTSnBdF7zQZ5_-g96XgKzlySaLI,4302
+matplotlib/contour.py,sha256=p8PJvj7N7wd-yA94NP7cz230VHmYG3rkSsdYSoTP4hE,68387
+matplotlib/dates.py,sha256=hGrXqJ-O1ahP2yCYK0HjFWJu0jt-EDke1S33VZ2uOe0,66867
+matplotlib/docstring.py,sha256=TuDl6xhQwCISvIV5_rPKvT85V1k1IA5HVSyK-JMY9k4,3970
+matplotlib/dviread.py,sha256=sO5Mq6CkNYqgu5S3oMcDc6Gqh1YhD36VoRWH9PbbIJw,39009
+matplotlib/figure.py,sha256=yQsOCdFl3HHez8H3Y6xylSKAAEZzy-Hl-b1-BSJ3r8k,100886
+matplotlib/font_manager.py,sha256=OZ2BAmPgFrO4VjeBA-DKGM0H0I5T4ev5gtAdirbXwR0,45282
+matplotlib/fontconfig_pattern.py,sha256=El7xTXT3EnEESVONX9bKz_Yvaq4lLQp3KIYBgNbAguI,6719
+matplotlib/ft2font.cpython-38-x86_64-linux-gnu.so,sha256=8ZalFUIbDG4e1r_hh9jOu1kktn6flWEH2TZHa4Xol_A,919008
+matplotlib/gridspec.py,sha256=k_YO0Enho4Ztxi9-rv-ZgSXs-rrqKq1TdeOGwOzJwQI,25046
+matplotlib/hatch.py,sha256=6AcnVeBs4d_Uv_FWQoU48v28iW5F7oKI_RKkv68L_WM,6971
+matplotlib/image.py,sha256=0FR_I8bEV0XfM6kYyAS8j965LCbtapgnpxDWLrEV390,62787
+matplotlib/legend.py,sha256=QBSq0VyGWFsTB7QFGx8bpmUR-MLw9xJPrhMcJSlpVtU,48268
+matplotlib/legend_handler.py,sha256=hDMDvDJepK6UUyTJCkGQcAMVgLYW7eD16hQoQNVGQFY,26428
+matplotlib/lines.py,sha256=8u3vEQXb_ceOivl4zyL5MIDuVDlxgZwOsH9xf37OIYo,51362
+matplotlib/markers.py,sha256=Xpuq9O1t7OB3DBK2nZa4IbPjqV0ROGY9mxXW_NUKDKk,32533
+matplotlib/mathtext.py,sha256=udfWXa-87xV1Mhve65rO0jf5On3DAQwJ-FgD6BSojhM,120491
+matplotlib/mlab.py,sha256=1V0nvAkZF5RJbsIAp3AfTgzim2wRQCql0FCq7HvLknI,49040
+matplotlib/mpl-data/fonts/afm/cmex10.afm,sha256=blR3ERmrVBV5XKkAnDCj4NMeYVgzH7cXtJ3u59u9GuE,12070
+matplotlib/mpl-data/fonts/afm/cmmi10.afm,sha256=5qwEOpedEo76bDUahyuuF1q0cD84tRrX-VQ4p3MlfBo,10416
+matplotlib/mpl-data/fonts/afm/cmr10.afm,sha256=WDvgC_D3UkGJg9u-J0U6RaT02lF4oz3lQxHtg1r3lYw,10101
+matplotlib/mpl-data/fonts/afm/cmsy10.afm,sha256=AbmzvCVWBceHRfmRfeJ9E6xzOQTFLk0U1zDfpf3_MaM,8295
+matplotlib/mpl-data/fonts/afm/cmtt10.afm,sha256=4ji7_mTpeWMa93o_UHBWPKCnqsBfhJJNllat1lJArP4,6501
+matplotlib/mpl-data/fonts/afm/pagd8a.afm,sha256=jjFrigwkTpYLqa26cpzZvKQNBo-PuF4bmDVqaM4pMWw,17183
+matplotlib/mpl-data/fonts/afm/pagdo8a.afm,sha256=sgNQdeYyx8J-itGw9h31y95aMBiTCRvmNSPTXwwS7xg,17255
+matplotlib/mpl-data/fonts/afm/pagk8a.afm,sha256=ZUtfHPloNqcvGMHMxaKDSlshhOcjwheUx143RwpGdIU,17241
+matplotlib/mpl-data/fonts/afm/pagko8a.afm,sha256=Yj1wBg6Jsqqz1KBfhRoJ3ACR-CMQol8Fj_ZM5NZ1gDk,17346
+matplotlib/mpl-data/fonts/afm/pbkd8a.afm,sha256=Zl5o6J_di9Y5j2EpHtjew-_sfg7-WoeVmO9PzOYSTUc,15157
+matplotlib/mpl-data/fonts/afm/pbkdi8a.afm,sha256=JAOno930iTyfZILMf11vWtiaTgrJcPpP6FRTRhEMMD4,15278
+matplotlib/mpl-data/fonts/afm/pbkl8a.afm,sha256=UJqJjOJ6xQDgDBLX157mKpohIJFVmHM-N6x2-DiGv14,15000
+matplotlib/mpl-data/fonts/afm/pbkli8a.afm,sha256=AWislZ2hDbs0ox_qOWREugsbS8_8lpL48LPMR40qpi0,15181
+matplotlib/mpl-data/fonts/afm/pcrb8a.afm,sha256=6j1TS2Uc7DWSc-8l42TGDc1u0Fg8JspeWfxFayjUwi8,15352
+matplotlib/mpl-data/fonts/afm/pcrbo8a.afm,sha256=smg3mjl9QaBDtQIt06ko5GvaxLsO9QtTvYANuE5hfG0,15422
+matplotlib/mpl-data/fonts/afm/pcrr8a.afm,sha256=7nxFr0Ehz4E5KG_zSE5SZOhxRH8MyfnCbw-7x5wu7tw,15339
+matplotlib/mpl-data/fonts/afm/pcrro8a.afm,sha256=NKEz7XtdFkh9cA8MvY-S3UOZlV2Y_J3tMEWFFxj7QSg,15443
+matplotlib/mpl-data/fonts/afm/phvb8a.afm,sha256=NAx4M4HjL7vANCJbc-tk04Vkol-T0oaXeQ3T2h-XUvM,17155
+matplotlib/mpl-data/fonts/afm/phvb8an.afm,sha256=8e_myD-AQkNF7q9XNLb2m76_lX2TUr3a5wog_LIE1sk,17086
+matplotlib/mpl-data/fonts/afm/phvbo8a.afm,sha256=8fkBRmJ-SWY2YrBg8fFyjJyrJp8daQ6JPO6LvhM8xPI,17230
+matplotlib/mpl-data/fonts/afm/phvbo8an.afm,sha256=aeVRvV4r15BBvxuRJ0MG8ZHuH2HViuIiCYkvuapmkmM,17195
+matplotlib/mpl-data/fonts/afm/phvl8a.afm,sha256=IyMYM-bgl-gI6rG0EuZZ2OLzlxJfGeSh8xqsh0t-eJQ,15627
+matplotlib/mpl-data/fonts/afm/phvlo8a.afm,sha256=s12C-eNnIDHJ_UVbuiprjxBjCiHIbS3Y8ORTC-qTpuI,15729
+matplotlib/mpl-data/fonts/afm/phvr8a.afm,sha256=Kt8KaRidts89EBIK29X2JomDUEDxvroeaJz_RNTi6r4,17839
+matplotlib/mpl-data/fonts/afm/phvr8an.afm,sha256=lL5fAHTRwODl-sB5mH7IfsD1tnnea4yRUK-_Ca2bQHM,17781
+matplotlib/mpl-data/fonts/afm/phvro8a.afm,sha256=3KqK3eejiR4hIFBUynuSX_4lMdE2V2T58xOF8lX-fwc,17919
+matplotlib/mpl-data/fonts/afm/phvro8an.afm,sha256=Vx9rRf3YfasMY7tz-njSxz67xHKk-fNkN7yBi0X2IP0,17877
+matplotlib/mpl-data/fonts/afm/pncb8a.afm,sha256=aoXepTcDQtQa_mspflMJkEFKefzXHoyjz6ioJVI0YNc,16028
+matplotlib/mpl-data/fonts/afm/pncbi8a.afm,sha256=pCWW1MYgy0EmvwaYsaYJaAI_LfrsKmDANHu7Pk0RaiU,17496
+matplotlib/mpl-data/fonts/afm/pncr8a.afm,sha256=0CIB2BLe9r-6_Wl5ObRTTf98UOrezmGQ8ZOuBX5kLks,16665
+matplotlib/mpl-data/fonts/afm/pncri8a.afm,sha256=5R-pLZOnaHNG8pjV6MP3Ai-d2OTQYR_cYCb5zQhzfSU,16920
+matplotlib/mpl-data/fonts/afm/pplb8a.afm,sha256=3EzUbNnXr5Ft5eFLY00W9oWu59rHORgDXUuJaOoKN58,15662
+matplotlib/mpl-data/fonts/afm/pplbi8a.afm,sha256=X_9tVspvrcMer3OS8qvdwjFFqpAXYZneyCL2NHA902g,15810
+matplotlib/mpl-data/fonts/afm/pplr8a.afm,sha256=ijMb497FDJ9nVdVMb21F7W3-cu9sb_9nF0oriFpSn8k,15752
+matplotlib/mpl-data/fonts/afm/pplri8a.afm,sha256=8KITbarcUUMi_hdoRLLmNHtlqs0TtOSKqtPFft7X5nY,15733
+matplotlib/mpl-data/fonts/afm/psyr.afm,sha256=Iyt8ajE4B2Tm34oBj2pKtctIf9kPfq05suQefq8p3Ro,9644
+matplotlib/mpl-data/fonts/afm/ptmb8a.afm,sha256=bL1fA1NC4_nW14Zrnxz4nHlXJb4dzELJPvodqKnYeMg,17983
+matplotlib/mpl-data/fonts/afm/ptmbi8a.afm,sha256=-_Ui6XlKaFTHEnkoS_-1GtIr5VtGa3gFQ2ezLOYHs08,18070
+matplotlib/mpl-data/fonts/afm/ptmr8a.afm,sha256=IEcsWcmzJyjCwkgsw4o6hIMmzlyXUglJat9s1PZNnEU,17942
+matplotlib/mpl-data/fonts/afm/ptmri8a.afm,sha256=49fQMg5fIGguZ7rgc_2styMK55Pv5bPTs7wCzqpcGpk,18068
+matplotlib/mpl-data/fonts/afm/putb8a.afm,sha256=qMaHTdpkrNL-m4DWhjpxJCSmgYkCv1qIzLlFfM0rl40,21532
+matplotlib/mpl-data/fonts/afm/putbi8a.afm,sha256=g7AVJyiTxeMpNk_1cSfmYgM09uNUfPlZyWGv3D1vcAk,21931
+matplotlib/mpl-data/fonts/afm/putr8a.afm,sha256=XYmNC5GQgSVAZKTIYdYeNksE6znNm9GF_0SmQlriqx0,22148
+matplotlib/mpl-data/fonts/afm/putri8a.afm,sha256=i7fVe-iLyLtQxCfAa4IxdxH-ufcHmMk7hbCGG5TxAY4,21891
+matplotlib/mpl-data/fonts/afm/pzcmi8a.afm,sha256=wyuoIWEZOcoXrSl1tPzLkEahik7kGi91JJj-tkFRG4A,16250
+matplotlib/mpl-data/fonts/afm/pzdr.afm,sha256=MyjLAnzKYRdQBfof1W3k_hf30MvqOkqL__G22mQ5xww,9467
+matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Bold.afm,sha256=sIDDI-B82VZ3C0mI_mHFITCZ7PVn37AIYMv1CrHX4sE,15333
+matplotlib/mpl-data/fonts/pdfcorefonts/Courier-BoldOblique.afm,sha256=zg61QobD3YU9UBfCXmvmhBNaFKno-xj8sY0b2RpgfLw,15399
+matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Oblique.afm,sha256=vRQm5j1sTUN4hicT1PcVZ9P9DTTUHhEzfPXqUUzVZhE,15441
+matplotlib/mpl-data/fonts/pdfcorefonts/Courier.afm,sha256=Mdcq2teZEBJrIqVXnsnhee7oZnTs6-P8_292kWGTrw4,15335
+matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Bold.afm,sha256=i2l4gcjuYXoXf28uK7yIVwuf0rnw6J7PwPVQeHj5iPw,69269
+matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-BoldOblique.afm,sha256=Um5O6qK11DXLt8uj_0IoWkc84TKqHK3bObSKUswQqvY,69365
+matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Oblique.afm,sha256=hVYDg2b52kqtbVeCzmiv25bW1yYdpkZS-LXlGREN2Rs,74392
+matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica.afm,sha256=23cvKDD7bQAJB3kdjSahJSTZaUOppznlIO6FXGslyW8,74292
+matplotlib/mpl-data/fonts/pdfcorefonts/Symbol.afm,sha256=P5UaoXr4y0qh4SiMa5uqijDT6ZDr2-jPmj1ayry593E,9740
+matplotlib/mpl-data/fonts/pdfcorefonts/Times-Bold.afm,sha256=cQTmr2LFPwKQE_sGQageMcmFicjye16mKJslsJLHQyE,64251
+matplotlib/mpl-data/fonts/pdfcorefonts/Times-BoldItalic.afm,sha256=pzWOdycm6RqocBWgAVY5Jq0z3Fp7LuqWgLNMx4q6OFw,59642
+matplotlib/mpl-data/fonts/pdfcorefonts/Times-Italic.afm,sha256=bK5puSMpGT_YUILwyJrXoxjfj7XJOdfv5TQ_iKsJRzw,66328
+matplotlib/mpl-data/fonts/pdfcorefonts/Times-Roman.afm,sha256=hhNrUnpazuDDKD1WpraPxqPWCYLrO7D7bMVOg-zI13o,60460
+matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm,sha256=ZuOmt9GcKofjdOq8kqhPhtAIhOwkL2rTJTmZxAjFakA,9527
+matplotlib/mpl-data/fonts/pdfcorefonts/readme.txt,sha256=MRv8ppSITYYAb7lt5EOw9DWWNZIblfxsFhu5TQE7cpI,828
+matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf,sha256=sYS4njwQdfIva3FXW2_CDUlys8_TsjMiym_Vltyu8Wc,704128
+matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf,sha256=bt8CgxYBhq9FHL7nHnuEXy5Mq_Jku5ks5mjIPCVGXm8,641720
+matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf,sha256=zN90s1DxH9PdV3TeUOXmNGoaXaH1t9X7g1kGZel6UhM,633840
+matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf,sha256=P99pyr8GBJ6nCgC1kZNA4s4ebQKwzDxLRPtoAb0eDSI,756072
+matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf,sha256=ggmdz7paqGjN_CdFGYlSX-MpL3N_s8ngMozpzvWWUvY,25712
+matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf,sha256=uq2ppRcv4giGJRr_BDP8OEYZEtXa8HKH577lZiCo2pY,331536
+matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf,sha256=ppCBwVx2yCfgonpaf1x0thNchDSZlVSV_6jCDTqYKIs,253116
+matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf,sha256=KAUoE_enCfyJ9S0ZLcmV708P3Fw9e3OknWhJsZFtDNA,251472
+matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf,sha256=YC7Ia4lIz82VZIL-ZPlMNshndwFJ7y95HUYT9EO87LM,340240
+matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf,sha256=w3U_Lta8Zz8VhG3EWt2-s7nIcvMvsY_VOiHxvvHtdnY,355692
+matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf,sha256=2T7-x6nS6CZ2jRou6VuVhw4V4pWZqE80hK8d4c7C4YE,347064
+matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf,sha256=PnmU-8VPoQzjNSpC1Uj63X2crbacsRCbydlg9trFfwQ,345612
+matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf,sha256=EHJElW6ZYrnpb6zNxVGCXgrgiYrhNzcTPhuSGi_TX_o,379740
+matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf,sha256=KRTzLkfHd8J75Wd6-ufbTeefnkXeb8kJfZlJwjwU99U,14300
+matplotlib/mpl-data/fonts/ttf/LICENSE_DEJAVU,sha256=11k43sCY8G8Kw8AIUwZdlPAgvhw8Yu8dwpdboVtNmw4,4816
+matplotlib/mpl-data/fonts/ttf/LICENSE_STIX,sha256=cxFOZdp1AxNhXR6XxCzf5iJpNcu-APm-geOHhD-s0h8,5475
+matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf,sha256=FnN4Ax4t3cYhbWeBnJJg6aBv_ExHjk4jy5im_USxg8I,448228
+matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf,sha256=6FM9xwg_o0a9oZM9YOpKg7Z9CUW86vGzVB-CtKDixqA,237360
+matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf,sha256=mHiP1LpI37sr0CbA4gokeosGxzcoeWKLemuw1bsJc2w,181152
+matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf,sha256=bPyzM9IrfDxiO9_UAXTxTIXD1nMcphZsHtyAFA6uhSc,175040
+matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf,sha256=Ulb34CEzWsSFTRgPDovxmJZOwvyCAXYnbhaqvGU3u1c,59108
+matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf,sha256=XRBqW3jR_8MBdFU0ObhiV7-kXwiBIMs7QVClHcT5tgs,30512
+matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf,sha256=pb22DnbDf2yQqizotc3wBDqFGC_g27YcCGJivH9-Le8,41272
+matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf,sha256=BMr9pWiBv2YIZdq04X4c3CgL6NPLUPrl64aV1N4w9Ug,46752
+matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf,sha256=wYuH1gYUpCuusqItRH5kf9p_s6mUD-9X3L5RvRtKSxs,13656
+matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf,sha256=yNdvjUoSmsZCULmD7SVq9HabndG9P4dPhboL1JpAf0s,12228
+matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf,sha256=-9xVMYL4_1rcO8FiCKrCfR4PaSmKtA42ddLGqwtei1w,15972
+matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf,sha256=cYexyo8rZcdqMlpa9fNF5a2IoXLUTZuIvh0JD1Qp0i4,12556
+matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf,sha256=0lbHzpndzJmO8S42mlkhsz5NbvJLQCaH5Mcc7QZRDzc,19760
+matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf,sha256=3eBc-VtYbhQU3BnxiypfO6eAzEu8BdDvtIJSFbkS2oY,12192
+matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf,sha256=XFSKCptbESM8uxHtUFSAV2cybwxhSjd8dWVByq6f3w0,15836
+matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf,sha256=MUCYHrA0ZqFiSE_PjIGlJZgMuv79aUgQqE7Dtu3kuo0,12116
+matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf,sha256=_sdxDuEwBDtADpu9CyIXQxV7sIqA2TZVBCUiUjq5UCk,15704
+matplotlib/mpl-data/fonts/ttf/cmb10.ttf,sha256=B0SXtQxD6ldZcYFZH5iT04_BKofpUQT1ZX_CSB9hojo,25680
+matplotlib/mpl-data/fonts/ttf/cmex10.ttf,sha256=ryjwwXByOsd2pxv6WVrKCemNFa5cPVTOGa_VYZyWqQU,21092
+matplotlib/mpl-data/fonts/ttf/cmmi10.ttf,sha256=MJKWW4gR_WpnZXmWZIRRgfwd0TMLk3-RWAjEhdMWI00,32560
+matplotlib/mpl-data/fonts/ttf/cmr10.ttf,sha256=Tdl2GwWMAJ25shRfVe5mF9CTwnPdPWxbPkP_YRD6m_Y,26348
+matplotlib/mpl-data/fonts/ttf/cmss10.ttf,sha256=ffkag9BbLkcexjjLC0NaNgo8eSsJ_EKn2mfpHy55EVo,20376
+matplotlib/mpl-data/fonts/ttf/cmsy10.ttf,sha256=uyJu2TLz8QDNDlL15JEu5VO0G2nnv9uNOFTbDrZgUjI,29396
+matplotlib/mpl-data/fonts/ttf/cmtt10.ttf,sha256=YhHwmuk1mZka_alwwkZp2tGnfiU9kVYk-_IS9wLwcdc,28136
+matplotlib/mpl-data/images/back.gif,sha256=sdkxFRAh-Mgs44DTvruO5OxcI3Av9CS1g5MqMA_DDkQ,608
+matplotlib/mpl-data/images/back.pdf,sha256=ZR7CJo_dAeCM-KlaGvskgtHQyRtrPIolc8REOmcoqJk,1623
+matplotlib/mpl-data/images/back.png,sha256=E4dGf4Gnz1xJ1v2tMygHV0YNQgShreDeVApaMb-74mU,380
+matplotlib/mpl-data/images/back.svg,sha256=yRdMiKsa-awUm2x_JE_rEV20rNTa7FInbFBEoMo-6ik,1512
+matplotlib/mpl-data/images/back_large.gif,sha256=tqCtecrxNrPuDCUj7FGs8UXWftljKcwgp5cSBBhXwiQ,799
+matplotlib/mpl-data/images/back_large.png,sha256=9A6hUSQeszhYONE4ZuH3kvOItM0JfDVu6tkfromCbsQ,620
+matplotlib/mpl-data/images/filesave.gif,sha256=wAyNwOPd9c-EIPwcUAlqHSfLmxq167nhDVppOWPy9UA,723
+matplotlib/mpl-data/images/filesave.pdf,sha256=P1EPPV2g50WTt8UaX-6kFoTZM1xVqo6S2H6FJ6Zd1ec,1734
+matplotlib/mpl-data/images/filesave.png,sha256=b7ctucrM_F2mG-DycTedG_a_y4pHkx3F-zM7l18GLhk,458
+matplotlib/mpl-data/images/filesave.svg,sha256=oxPVbLS9Pzelz71C1GCJWB34DZ0sx_pUVPRHBrCZrGs,2029
+matplotlib/mpl-data/images/filesave_large.gif,sha256=IXrenlwu3wwO8WTRvxHt_q62NF6ZWyqk3jZhm6GE-G8,1498
+matplotlib/mpl-data/images/filesave_large.png,sha256=LNbRD5KZ3Kf7nbp-stx_a1_6XfGBSWUfDdpgmnzoRvk,720
+matplotlib/mpl-data/images/forward.gif,sha256=VNL9R-dECOX7wUAYPtU_DWn5hwi3SwLR17DhmBvUIxE,590
+matplotlib/mpl-data/images/forward.pdf,sha256=KIqIL4YId43LkcOxV_TT5uvz1SP8k5iUNUeJmAElMV8,1630
+matplotlib/mpl-data/images/forward.png,sha256=pKbLepgGiGeyY2TCBl8svjvm7Z4CS3iysFxcq4GR-wk,357
+matplotlib/mpl-data/images/forward.svg,sha256=NnQDOenfjsn-o0aJMUfErrP320Zcx9XHZkLh0cjMHsk,1531
+matplotlib/mpl-data/images/forward_large.gif,sha256=H6Jbcc7qJwHJAE294YqI5Bm-5irofX40cKRvYdrG_Ig,786
+matplotlib/mpl-data/images/forward_large.png,sha256=36h7m7DZDHql6kkdpNPckyi2LKCe_xhhyavWARz_2kQ,593
+matplotlib/mpl-data/images/hand.gif,sha256=3lRfmAqQU7A2t1YXXsB9IbwzK7FaRh-IZO84D5-xCrw,1267
+matplotlib/mpl-data/images/hand.pdf,sha256=hspwkNY915KPD7AMWnVQs7LFPOtlcj0VUiLu76dMabQ,4172
+matplotlib/mpl-data/images/hand.png,sha256=2cchRETGKa0hYNKUxnJABwkyYXEBPqJy_VqSPlT0W2Q,979
+matplotlib/mpl-data/images/hand.svg,sha256=tsVIES_nINrAbH4FqdsCGOx0SVE37vcofSYBhnnaOP0,4888
+matplotlib/mpl-data/images/hand_large.gif,sha256=H5IHmVTvOqHQb9FZ_7g7AlPt9gv-zRq0L5_Q9B7OuvU,973
+matplotlib/mpl-data/images/help.pdf,sha256=CeE978IMi0YWznWKjIT1R8IrP4KhZ0S7usPUvreSgcA,1813
+matplotlib/mpl-data/images/help.png,sha256=s4pQrqaQ0py8I7vc9hv3BI3DO_tky-7YBMpaHuBDCBY,472
+matplotlib/mpl-data/images/help.ppm,sha256=mVPvgwcddzCM-nGZd8Lnl_CorzDkRIXQE17b7qo8vlU,1741
+matplotlib/mpl-data/images/help.svg,sha256=KXabvQhqIWen_t2SvZuddFYa3S0iI3W8cAKm3s1fI8Q,1870
+matplotlib/mpl-data/images/help_large.png,sha256=1IwEyWfGRgnoCWM-r9CJHEogTJVD5n1c8LXTK4AJ4RE,747
+matplotlib/mpl-data/images/help_large.ppm,sha256=MiCSKp1Su88FXOi9MTtkQDA2srwbX3w5navi6cneAi4,6925
+matplotlib/mpl-data/images/home.gif,sha256=NKuFM7tTtFngdfsOpJ4AxYTL8PYS5GWKAoiJjBMwLlU,666
+matplotlib/mpl-data/images/home.pdf,sha256=e0e0pI-XRtPmvUCW2VTKL1DeYu1pvPmUUeRSgEbWmik,1737
+matplotlib/mpl-data/images/home.png,sha256=IcFdAAUa6_A0qt8IO3I8p4rpXpQgAlJ8ndBECCh7C1w,468
+matplotlib/mpl-data/images/home.svg,sha256=n_AosjJVXET3McymFuHgXbUr5vMLdXK2PDgghX8Cch4,1891
+matplotlib/mpl-data/images/home_large.gif,sha256=k86PJCgED46sCFkOlUYHA0s5U7OjRsc517bpAtU2JSw,1422
+matplotlib/mpl-data/images/home_large.png,sha256=uxS2O3tWOHh1iau7CaVV4ermIJaZ007ibm5Z3i8kXYg,790
+matplotlib/mpl-data/images/matplotlib.pdf,sha256=BkSUf-2xoij-eXfpV2t7y1JFKG1zD1gtV6aAg3Xi_wE,22852
+matplotlib/mpl-data/images/matplotlib.png,sha256=w8KLRYVa-voUZXa41hgJauQuoois23f3NFfdc72pUYY,1283
+matplotlib/mpl-data/images/matplotlib.svg,sha256=QiTIcqlQwGaVPtHsEk-vtmJk1wxwZSvijhqBe_b9VCI,62087
+matplotlib/mpl-data/images/matplotlib_128.ppm,sha256=IHPRWXpLFRq3Vb7UjiCkFrN_N86lSPcfrEGunST08d8,49167
+matplotlib/mpl-data/images/matplotlib_large.png,sha256=ElRoue9grUqkZXJngk-nvh4GKfpvJ4gE69WryjCbX5U,3088
+matplotlib/mpl-data/images/move.gif,sha256=FN52MptH4FZiwmV2rQgYCO2FvO3m5LtqYv8jk6Xbeyk,679
+matplotlib/mpl-data/images/move.pdf,sha256=CXk3PGK9WL5t-5J-G2X5Tl-nb6lcErTBS5oUj2St6aU,1867
+matplotlib/mpl-data/images/move.png,sha256=TmjR41IzSzxGbhiUcV64X0zx2BjrxbWH3cSKvnG2vzc,481
+matplotlib/mpl-data/images/move.svg,sha256=_ZKpcwGD6DMTkZlbyj0nQbT8Ygt5vslEZ0OqXaXGd4E,2509
+matplotlib/mpl-data/images/move_large.gif,sha256=RMIAr-G9OOY7vWC04oN6qv5TAHJxhQGhLsw_bNsvWbg,951
+matplotlib/mpl-data/images/move_large.png,sha256=Skjz2nW_RTA5s_0g88gdq2hrVbm6DOcfYW4Fu42Fn9U,767
+matplotlib/mpl-data/images/qt4_editor_options.pdf,sha256=2qu6GVyBrJvVHxychQoJUiXPYxBylbH2j90QnytXs_w,1568
+matplotlib/mpl-data/images/qt4_editor_options.png,sha256=EryQjQ5hh2dwmIxtzCFiMN1U6Tnd11p1CDfgH5ZHjNM,380
+matplotlib/mpl-data/images/qt4_editor_options.svg,sha256=E00YoX7u4NrxMHm_L1TM8PDJ88bX5qRdCrO-Uj59CEA,1244
+matplotlib/mpl-data/images/qt4_editor_options_large.png,sha256=-Pd-9Vh5aIr3PZa8O6Ge_BLo41kiEnpmkdDj8a11JkY,619
+matplotlib/mpl-data/images/subplots.gif,sha256=QfhmUdcrko08-WtrzCJUjrVFDTvUZCJEXpARNtzEwkg,691
+matplotlib/mpl-data/images/subplots.pdf,sha256=Q0syPMI5EvtgM-CE-YXKOkL9eFUAZnj_X2Ihoj6R4p4,1714
+matplotlib/mpl-data/images/subplots.png,sha256=MUfCItq3_yzb9yRieGOglpn0Y74h8IA7m5i70B63iRc,445
+matplotlib/mpl-data/images/subplots.svg,sha256=8acBogXIr9OWGn1iD6mUkgahdFZgDybww385zLCLoIs,2130
+matplotlib/mpl-data/images/subplots_large.gif,sha256=Ff3ERmtVAaGP9i1QGUNnIIKac6LGuSW2Qf4DrockZSI,1350
+matplotlib/mpl-data/images/subplots_large.png,sha256=Edu9SwVMQEXJZ5ogU5cyW7VLcwXJdhdf-EtxxmxdkIs,662
+matplotlib/mpl-data/images/zoom_to_rect.gif,sha256=mTX6h9fh2W9zmvUYqeibK0TZ7qIMKOB1nAXMpD_jDys,696
+matplotlib/mpl-data/images/zoom_to_rect.pdf,sha256=SEvPc24gfZRpl-dHv7nx8KkxPyU66Kq4zgQTvGFm9KA,1609
+matplotlib/mpl-data/images/zoom_to_rect.png,sha256=aNz3QZBrIgxu9E-fFfaQweCVNitGuDUFoC27e5NU2L4,530
+matplotlib/mpl-data/images/zoom_to_rect.svg,sha256=1vRxr3cl8QTwTuRlQzD1jxu0fXZofTJ2PMgG97E7Bco,1479
+matplotlib/mpl-data/images/zoom_to_rect_large.gif,sha256=nx5LUpTAH6ZynM3ZfZDS-wR87jbMUsUnyQ27NGkV0_c,1456
+matplotlib/mpl-data/images/zoom_to_rect_large.png,sha256=V6pkxmm6VwFExdg_PEJWdK37HB7k3cE_corLa7RbUMk,1016
+matplotlib/mpl-data/matplotlibrc,sha256=ZDuS3NjqmSy4_oIXNk9mMNcD74ExJ9clpz6fbgSVMCk,40282
+matplotlib/mpl-data/sample_data/Minduka_Present_Blue_Pack.png,sha256=XnKGiCanpDKalQ5anvo5NZSAeDP7fyflzQAaivuc0IE,13634
+matplotlib/mpl-data/sample_data/None_vs_nearest-pdf.png,sha256=5CPvcG3SDNfOXx39CMKHCNS9JKZ-fmOUwIfpppNXsQ0,106228
+matplotlib/mpl-data/sample_data/README.txt,sha256=ABz19VBKfGewdY39QInG9Qccgn1MTYV3bT5Ph7TCy2Y,128
+matplotlib/mpl-data/sample_data/aapl.npz,sha256=GssVYka_EccteiXbNRJJ5GsuqU7G8F597qX7srYXZsw,107503
+matplotlib/mpl-data/sample_data/ada.png,sha256=X1hjJK1_1Nc8DN-EEhey3G7Sq8jBwQDKNSl4cCAE0uY,308313
+matplotlib/mpl-data/sample_data/axes_grid/bivariate_normal.npy,sha256=DpWZ9udAh6ospYqneEa27D6EkRgORFwHosacZXVu98U,1880
+matplotlib/mpl-data/sample_data/ct.raw.gz,sha256=LDvvgH-mycRQF2D29-w5MW94ZI0opvwKUoFI8euNpMk,256159
+matplotlib/mpl-data/sample_data/data_x_x2_x3.csv,sha256=A0SU3buOUGhT-NI_6LQ6p70fFSIU3iLFdgzvzrKR6SE,132
+matplotlib/mpl-data/sample_data/demodata.csv,sha256=MRybziqnyrqMCH9qG7Mr6BwcohIhftVG5dejXV2AX2M,659
+matplotlib/mpl-data/sample_data/eeg.dat,sha256=KGVjFt8ABKz7p6XZirNfcxSTOpGGNuyA8JYErRKLRBc,25600
+matplotlib/mpl-data/sample_data/embedding_in_wx3.xrc,sha256=cUqVw5vDHNSZoaO4J0ebZUf5SrJP36775abs7R9Bclg,2186
+matplotlib/mpl-data/sample_data/goog.npz,sha256=QAkXzzDmtmT3sNqT18dFhg06qQCNqLfxYNLdEuajGLE,22845
+matplotlib/mpl-data/sample_data/grace_hopper.jpg,sha256=qMptc0dlcDsJcoq0f-WfRz2Trjln_CTHwCiMPHrbcTA,61306
+matplotlib/mpl-data/sample_data/grace_hopper.png,sha256=MCf0ju2kpC40srQ0xw4HEyOoKhLL4khP3jHfU9_dR7s,628280
+matplotlib/mpl-data/sample_data/jacksboro_fault_dem.npz,sha256=1JP1CjPoKkQgSUxU0fyhU50Xe9wnqxkLxf5ukvYvtjc,174061
+matplotlib/mpl-data/sample_data/logo2.png,sha256=ITxkJUsan2oqXgJDy6DJvwJ4aHviKeWGnxPkTjXUt7A,33541
+matplotlib/mpl-data/sample_data/membrane.dat,sha256=q3lbQpIBpbtXXGNw1eFwkN_PwxdDGqk4L46IE2b0M1c,48000
+matplotlib/mpl-data/sample_data/msft.csv,sha256=GArKb0O3DgKZRsKdJf6lX3rMSf-PCekIiBoLNdgF7Mk,3211
+matplotlib/mpl-data/sample_data/percent_bachelors_degrees_women_usa.csv,sha256=TzoqamsV_N3d3lW7SKmj14zZVX4FOOg9jJcsC5U9pbA,5681
+matplotlib/mpl-data/sample_data/s1045.ima.gz,sha256=MrQk1k9it-ccsk0p_VOTitVmTWCAVaZ6srKvQ2n4uJ4,33229
+matplotlib/mpl-data/sample_data/topobathy.npz,sha256=AkTgMpFwLfRQJNy1ysvE89TLMNct-n_TccSsYcQrT78,45224
+matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle,sha256=PECeO60wwJe2sSDvxapBJRuKGek0qLcoaN8qOX6tgNQ,1255
+matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle,sha256=iopHpMaM3im_AK2aiHGuM2DKM5i9Kc84v6NQEoSb10Q,167
+matplotlib/mpl-data/stylelib/bmh.mplstyle,sha256=-KbhaI859BITHIoyUZIfpQDjfckgLAlDAS_ydKsm6mc,712
+matplotlib/mpl-data/stylelib/classic.mplstyle,sha256=0RjtrDi0vZOzWGnt9cme_At_9GqMwHzwEBCAH9OQZ7I,24511
+matplotlib/mpl-data/stylelib/dark_background.mplstyle,sha256=-EGmoFm_35Zk7oRp29UalT56HsOSuJbYMeQGdAATnz4,477
+matplotlib/mpl-data/stylelib/fast.mplstyle,sha256=yTa2YEIIP9xi5V_G0p2vSlxghuhNwjRi9gPECMxyRiM,288
+matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle,sha256=WNUmAFuBPcqQPVgt6AS1ldy8Be2XO01N-1YQL__Q6ZY,832
+matplotlib/mpl-data/stylelib/ggplot.mplstyle,sha256=xhjLwr8hiikEXKy8APMy0Bmvtz1g0WnG84gX7e9lArs,957
+matplotlib/mpl-data/stylelib/grayscale.mplstyle,sha256=KCLg-pXpns9cnKDXKN2WH6mV41OH-6cbT-5zKQotSdw,526
+matplotlib/mpl-data/stylelib/seaborn-bright.mplstyle,sha256=pDqn3-NUyVLvlfkYs8n8HzNZvmslVMChkeH-HtZuJIc,144
+matplotlib/mpl-data/stylelib/seaborn-colorblind.mplstyle,sha256=eCSzFj5_2vR6n5qu1rHE46wvSVGZcdVqz85ov40ZsH8,148
+matplotlib/mpl-data/stylelib/seaborn-dark-palette.mplstyle,sha256=p5ABKNQHRG7bk4HXqMQrRBjDlxGAo3RCXHdQmP7g-Ng,142
+matplotlib/mpl-data/stylelib/seaborn-dark.mplstyle,sha256=I4xQ75vE5_9X4k0cNDiqhhnF3OcrZ2xlPX8Ll7OCkoE,667
+matplotlib/mpl-data/stylelib/seaborn-darkgrid.mplstyle,sha256=2bXOSzS5gmPzRBrRmzVWyhg_7ZaBRQ6t_-O-cRuyZoA,670
+matplotlib/mpl-data/stylelib/seaborn-deep.mplstyle,sha256=44dLcXjjRgR-6yaopgGRInaVgz3jk8VJVQTbBIcxRB0,142
+matplotlib/mpl-data/stylelib/seaborn-muted.mplstyle,sha256=T4o3jvqKD_ImXDkp66XFOV_xrBVFUolJU34JDFk1Xkk,143
+matplotlib/mpl-data/stylelib/seaborn-notebook.mplstyle,sha256=PcvZQbYrDdducrNlavBPmQ1g2minio_9GkUUFRdgtoM,382
+matplotlib/mpl-data/stylelib/seaborn-paper.mplstyle,sha256=n0mboUp2C4Usq2j6tNWcu4TZ_YT4-kKgrYO0t-rz1yw,393
+matplotlib/mpl-data/stylelib/seaborn-pastel.mplstyle,sha256=8nV8qRpbUrnFZeyE6VcQ1oRuZPLil2W74M2U37DNMOE,144
+matplotlib/mpl-data/stylelib/seaborn-poster.mplstyle,sha256=dUaKqTE4MRfUq2rWVXbbou7kzD7Z9PE9Ko8aXLza8JA,403
+matplotlib/mpl-data/stylelib/seaborn-talk.mplstyle,sha256=7FnBaBEdWBbncTm6_ER-EQVa_bZgU7dncgez-ez8R74,403
+matplotlib/mpl-data/stylelib/seaborn-ticks.mplstyle,sha256=CITZmZFUFp40MK2Oz8tI8a7WRoCizQU9Z4J172YWfWw,665
+matplotlib/mpl-data/stylelib/seaborn-white.mplstyle,sha256=WjJ6LEU6rlCwUugToawciAbKP9oERFHr9rfFlUrdTx0,665
+matplotlib/mpl-data/stylelib/seaborn-whitegrid.mplstyle,sha256=ec4BjsNzmOvHptcJ3mdPxULF3S1_U1EUocuqfIpw-Nk,664
+matplotlib/mpl-data/stylelib/seaborn.mplstyle,sha256=_Xu6qXKzi4b3GymCOB1b1-ykKTQ8xhDliZ8ezHGTiAs,1130
+matplotlib/mpl-data/stylelib/tableau-colorblind10.mplstyle,sha256=BsirZVd1LmPWT4tBIz6loZPjZcInoQrIGfC7rvzqmJw,190
+matplotlib/offsetbox.py,sha256=1oReQSHV50aRsHqHIa3Km234FXx8sUZRLdiGbdGu2zU,58812
+matplotlib/patches.py,sha256=4JTt8OKfTg_BzSQVXYjsQLAFbZvvzR9T-1zsajaPpx8,151886
+matplotlib/path.py,sha256=ov2ghbmWhCV-PhxUPGEfsG409Nrkf9HP4u7oQSpGFtc,37640
+matplotlib/patheffects.py,sha256=ERzaWP5RZTnaEVWx77c8oB-JQeMpVJtY-hBdRqkQ4ZM,13235
+matplotlib/projections/__init__.py,sha256=4b447LtxkDz1nC1B9tb_J7cCpq1fjxwaEvzkHy-Y3KA,1816
+matplotlib/projections/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/projections/__pycache__/geo.cpython-38.pyc,,
+matplotlib/projections/__pycache__/polar.cpython-38.pyc,,
+matplotlib/projections/geo.py,sha256=OrTW_dk4QqrTv9lYBean9uz24MBIWyWcnkADyTOdNuc,17690
+matplotlib/projections/polar.py,sha256=1aHOdhpaJKpu5BNS2eRoeIgej-QUunRpIGcxXkv1s28,52508
+matplotlib/pylab.py,sha256=VOs5onux2-yPGn0exAPsR75U9ty2hOzvg8Nz1K7TWBg,9595
+matplotlib/pyplot.py,sha256=vM3sVw0yIb0EbehwlLAOWHfGnMSN4W9OfTDmcsBELvs,111042
+matplotlib/quiver.py,sha256=B3CGGxSNVLwOPx2IgyG2DCSPQHs1MAS0o9sL_4Mm7rY,48018
+matplotlib/rcsetup.py,sha256=BhPaDqHrifbncC47epnKBtvlkk-MzhNp1HlVYgcgvF8,58589
+matplotlib/sankey.py,sha256=3PygeZ3h19RFICw1hL6qt5TwRoBrP1AKSznWvKHc6Z4,36986
+matplotlib/scale.py,sha256=_G5Aq3ta2vc2WOLuL3_UlnZ8XsXbGRrwBuqAG_OxCxU,25209
+matplotlib/sphinxext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+matplotlib/sphinxext/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/sphinxext/__pycache__/mathmpl.cpython-38.pyc,,
+matplotlib/sphinxext/__pycache__/plot_directive.cpython-38.pyc,,
+matplotlib/sphinxext/mathmpl.py,sha256=jTqwBfxLLGjx-4TwEzwYvi2WdCoJpqHfDcbLKrgcKss,4276
+matplotlib/sphinxext/plot_directive.py,sha256=dAECzhJ1qVCq3nN3poGjKK2AAaAIwGikEHQIz_V0U0Y,26574
+matplotlib/spines.py,sha256=Xl9hIsBHzYaGq79UQb3Amfwfrq1-ZNsiS4wpPwstbp8,21227
+matplotlib/stackplot.py,sha256=fj4oqFbYA1G3RaTnV_Jic5mn5K3rza-JYp6jGR7e0yk,3917
+matplotlib/streamplot.py,sha256=9zniqozPg42NfXk4bx_Ro2RlkYYuR2CEJwJfJkt7jtI,22641
+matplotlib/style/__init__.py,sha256=EExOAUAq3u_rscUwkfKtZoEgLA5npmltCrYZOP9ftjw,67
+matplotlib/style/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/style/__pycache__/core.cpython-38.pyc,,
+matplotlib/style/core.py,sha256=EAubJQ90H__1bljwNfjpT8kxHp30r6KtXoveLvnfs4Q,8222
+matplotlib/table.py,sha256=FXhhFOy_-ZkHwyphkjFBQku1WgDjR3iYlmV4Wvls0cY,26674
+matplotlib/testing/__init__.py,sha256=MVV0ao_RHOKgDhpSN7QJBnyunje7tDeco-No_HtvvcQ,1428
+matplotlib/testing/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/testing/__pycache__/compare.cpython-38.pyc,,
+matplotlib/testing/__pycache__/conftest.cpython-38.pyc,,
+matplotlib/testing/__pycache__/decorators.cpython-38.pyc,,
+matplotlib/testing/__pycache__/disable_internet.cpython-38.pyc,,
+matplotlib/testing/__pycache__/exceptions.cpython-38.pyc,,
+matplotlib/testing/compare.py,sha256=67rAW447H4AUnGoT-AUP7s6CB2Q1N4oqhetFdvBdKZk,16969
+matplotlib/testing/conftest.py,sha256=juolFb13qZ_jwO6DsOxkfCKuPmPsQwbY6Oznx76tLYo,4317
+matplotlib/testing/decorators.py,sha256=t9XYPto5sIm4OObU5dyF6z5FjdhuNE2saBr-dnZ45wU,17704
+matplotlib/testing/disable_internet.py,sha256=ovCho7Nu6w-uoJeUPjJS7XGKJN0ktSNyF6NODaEyjb4,4925
+matplotlib/testing/exceptions.py,sha256=72QmjiHG7DwxSvlJf8mei-hRit5AH3NKh0-osBo4YbY,138
+matplotlib/testing/jpl_units/Duration.py,sha256=Leomw6a4XHddFgqMoYj63HfxV_-u6_MuQ3iaQ26TJcg,4946
+matplotlib/testing/jpl_units/Epoch.py,sha256=XpQMTIOs6VIVzuFYxRcv6JUuz4kqVCb1nGfBpYYbeJA,6305
+matplotlib/testing/jpl_units/EpochConverter.py,sha256=TAhtAyDHvvxDJL036DWEXrkZl_CwpVl2y5FNXloAoxo,4067
+matplotlib/testing/jpl_units/StrConverter.py,sha256=36hFTYbCM3Dh9_QR82dBbHh_7DSpsEthpnJYRICLlIc,4107
+matplotlib/testing/jpl_units/UnitDbl.py,sha256=sL0U984sJmka_0gzp6uhxSJuxnuh77aDAjtfCYdYJZ0,7793
+matplotlib/testing/jpl_units/UnitDblConverter.py,sha256=M5h3lOB4IEysqnR7VbkjUnMPS7wBtrZYWKvU3JUaJtI,4145
+matplotlib/testing/jpl_units/UnitDblFormatter.py,sha256=CRcbPtE3K0FlFJ4hkhi-SgQl1MUV-VlmIeOPIEPNwuI,681
+matplotlib/testing/jpl_units/__init__.py,sha256=Bp4Lz_eqRuxNWO_l3VvhuurFZaUmZIlvcF3N0UBmzHc,2692
+matplotlib/testing/jpl_units/__pycache__/Duration.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/Epoch.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/EpochConverter.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/StrConverter.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/UnitDbl.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/UnitDblConverter.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/UnitDblFormatter.cpython-38.pyc,,
+matplotlib/testing/jpl_units/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/tests/__init__.py,sha256=ns6SIKdszYNXD5h5PqKRCR06Z45H-sXrUX2VwujSRIM,366
+matplotlib/tests/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/tests/__pycache__/conftest.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_afm.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_agg.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_agg_filter.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_animation.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_arrow_patches.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_artist.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_axes.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_bases.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_cairo.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_nbagg.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_pdf.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_pgf.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_ps.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_qt.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_svg.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_tk.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_tools.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backend_webagg.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_backends_interactive.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_basic.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_bbox_tight.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_category.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_cbook.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_collections.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_colorbar.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_colors.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_compare_images.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_constrainedlayout.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_container.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_contour.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_cycles.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_dates.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_determinism.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_dviread.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_figure.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_font_manager.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_fontconfig_pattern.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_gridspec.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_image.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_legend.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_lines.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_marker.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_mathtext.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_matplotlib.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_mlab.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_offsetbox.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_patches.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_path.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_patheffects.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_pickle.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_png.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_preprocess_data.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_pyplot.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_quiver.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_rcparams.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_sankey.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_scale.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_simplification.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_skew.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_sphinxext.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_spines.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_streamplot.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_style.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_subplots.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_table.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_testing.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_texmanager.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_text.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_ticker.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_tightlayout.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_transforms.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_triangulation.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_ttconv.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_type1font.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_units.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_usetex.cpython-38.pyc,,
+matplotlib/tests/__pycache__/test_widgets.cpython-38.pyc,,
+matplotlib/tests/cmr10.pfb,sha256=_c7eh5QBjfXytY8JBfsgorQY7Y9ntz7hJEWFXfvlsb4,35752
+matplotlib/tests/conftest.py,sha256=QtpdWPUoXL_9F8WIytDc3--h0nPjbo8PToig7svIT1Y,258
+matplotlib/tests/mpltest.ttf,sha256=Jwb2O5KRVk_2CMqnhL0igeI3iGQCY3eChyS16N589zE,2264
+matplotlib/tests/test_afm.py,sha256=DGVfvABg6FRmbAq2ldRhM2wlqNfVrmRtSz12MCyqDXk,3710
+matplotlib/tests/test_agg.py,sha256=SC6WsFbq9Hs4dokpEqGqGPaFGS7zJpNxKRIRLUzD3Qk,7558
+matplotlib/tests/test_agg_filter.py,sha256=sfntvGVUuCaGqU3DdOnTRXqq0P1afVqWuaV_ZEYh8kQ,969
+matplotlib/tests/test_animation.py,sha256=WZ2kU7tjlDXL5Ryf89irQok884cWGtkVOJpNSjVyj3o,8916
+matplotlib/tests/test_arrow_patches.py,sha256=3EB9HIfdLCg_Nvq8zxEH9LJAV5OUbo6yndbZ-lIVcnQ,5593
+matplotlib/tests/test_artist.py,sha256=hMSfbg8Le0Li-vLEk2N9tZQtC1cUUE5Xn0O4CgYLugs,9418
+matplotlib/tests/test_axes.py,sha256=nRp8gbewUffIIfUBFlBxkq5E6FnH75wrCFY4Xp4GpG8,216153
+matplotlib/tests/test_backend_bases.py,sha256=I3Oiqk6Fm-fctj-aOxq5Av8Kqe3QLjlY-oTwTbdO6dw,3811
+matplotlib/tests/test_backend_cairo.py,sha256=gIjbQesBuvIBdpzoXXSdRmq3E2axCVX4h1Qfv6PNeKM,1936
+matplotlib/tests/test_backend_nbagg.py,sha256=dp9pYxWXN8ZBBjJcVE-1tJ61K7oeWtzcqIsAd7ABnfk,948
+matplotlib/tests/test_backend_pdf.py,sha256=GUqvImL8-HgpZGG0Zb41AE3KqVwtt8i00xcPutzGM1w,6696
+matplotlib/tests/test_backend_pgf.py,sha256=GAArsyhcLvWIHDtKgdv2kscRlgpNQ_oqyOXVGdVTw3Q,8636
+matplotlib/tests/test_backend_ps.py,sha256=E3eheQhOHcUhub4IzcWcDVvvnnVC6bHn6tv1NAF_KA4,3633
+matplotlib/tests/test_backend_qt.py,sha256=Oqtl2ZuS8Ly_ouDGkwndSheCs8U9OSiwBwJnnMmVGXI,9798
+matplotlib/tests/test_backend_svg.py,sha256=rPppxppM-G4mm4ISP4hq7pIVGTfS6Zyydt1jUoY2QqY,5863
+matplotlib/tests/test_backend_tk.py,sha256=sLqemf0mAFMIpv2414wKSVInLekL8bvzzaeREhE2UBU,934
+matplotlib/tests/test_backend_tools.py,sha256=C-B7NCkyWsQ5KzQEnI5Be16DsAHHZJU9P5v9--wsF-o,501
+matplotlib/tests/test_backend_webagg.py,sha256=u-UvO04iqxZbFlUrNIfyD0KIAxEhSOyqlrOqxO4DnDw,702
+matplotlib/tests/test_backends_interactive.py,sha256=QsdZhWATJPh3ZYM2F90MRQCyoW3ROUOGXYgrM0XUxsc,5018
+matplotlib/tests/test_basic.py,sha256=KRMwLx3J53urIx689lWHzkWkaWGTf0ZwhTyGrFDUfao,1268
+matplotlib/tests/test_bbox_tight.py,sha256=3nrCNbVtd-0K3LvndlPQBG9raTLdbXuDmoMWt5oVMSY,3902
+matplotlib/tests/test_category.py,sha256=SKBDW4R4q_7A0K1H-q801mTivxBgcLXigw4E6-tb1P4,10011
+matplotlib/tests/test_cbook.py,sha256=Z2Hp8UkeebK-BglQXm9WhiTpZC8CbSsSKUNy6hJoJbU,18684
+matplotlib/tests/test_collections.py,sha256=9Lyyydi1Ykk0BKXXWDPSYUQ1M9F-uJdVwqaZE1ZMKxI,23520
+matplotlib/tests/test_colorbar.py,sha256=OW2F_D6Vs7jEV8HOBh2kv_iV21u7sdyIqJcRakwEkTg,22065
+matplotlib/tests/test_colors.py,sha256=KYXZa1yFlkEGriaLMm-v2WnhROS2lvOUgseSHV2xC7w,33376
+matplotlib/tests/test_compare_images.py,sha256=n3Uoukid0GcjyQpd6ZrqIY9u3RLNE2XAPWwtcHIsqto,3155
+matplotlib/tests/test_constrainedlayout.py,sha256=9689kXb21xPGT3rqgZMxVT8HT4HXlzjO_aS6wq7b8kM,12981
+matplotlib/tests/test_container.py,sha256=75Di6cic-JY7JbOnsaz7N6Hcm3KUAJKaKbCoVSDPAuU,576
+matplotlib/tests/test_contour.py,sha256=G4dN6t8yrbpKp0aEI1VTrb-woyBKidGUqdDJ3b5D55U,12018
+matplotlib/tests/test_cycles.py,sha256=mgYXt63ov7th7IwGpLEoCh-ImubEp6wltzTbDchfQYI,7112
+matplotlib/tests/test_dates.py,sha256=1oDBr8xj2v_5tLHP_1rMkKA2nikoXSxK5u44ekL_NLA,32805
+matplotlib/tests/test_determinism.py,sha256=bu8E_NMdeN0X2KRMStjAgK3tklL5SG_IqYu4hov6Z-4,4586
+matplotlib/tests/test_dviread.py,sha256=kTk9Qv6q9Kk3fJcDAEWm35HF-sKsP6Ybec6N8jEHasE,2342
+matplotlib/tests/test_figure.py,sha256=EKv9PCtr5KU30dZ_tBjxfgrQryxYjf1u0YIS83mFfe8,15244
+matplotlib/tests/test_font_manager.py,sha256=OrOm9CD4FnUnSzoQEpRNcsDktw7gNBz3h7YMPGkudjI,7217
+matplotlib/tests/test_fontconfig_pattern.py,sha256=1MhBrF914MKxjbpdxKDqkfVyCZGnFBA06D2I6rqHRHo,2012
+matplotlib/tests/test_gridspec.py,sha256=zahj5Rd4pB0xtAc_3KX7fQWyBys0P-IQk-Cq0cs8VgY,626
+matplotlib/tests/test_image.py,sha256=RMReqvIC2celG9NhUkJzv7Cl2us5Pc3f8I4uhxAzGQs,36254
+matplotlib/tests/test_legend.py,sha256=oXMRtvzQgYMqNC0vPR8mhqQQl5n-IZRBSIZbr_N4hVk,20051
+matplotlib/tests/test_lines.py,sha256=zbpM3DO0F-aclKrTE2JliUZpBR1UV5coR6I5OXOvJbg,5970
+matplotlib/tests/test_marker.py,sha256=yGEoHoMji04-BPN-mmddO60pcpGwvL6FMO101xz70dg,4768
+matplotlib/tests/test_mathtext.py,sha256=xqmDclBoB-7253xFBgf_yI9JxdkYZfuYgJ66HFcyqQA,13331
+matplotlib/tests/test_matplotlib.py,sha256=DIBqISzUIYanSxNWJL9n2oob1dRLOOAr6TIz2BTWK1I,706
+matplotlib/tests/test_mlab.py,sha256=ZpoL_7s2ARzt3uhq1InSVEWahB-svI0siIutKqyyUpM,89334
+matplotlib/tests/test_offsetbox.py,sha256=f_I8sNuPmN4TXQAKGp5w3XQvnpWVSrHgEZr_0ZR7siE,6633
+matplotlib/tests/test_patches.py,sha256=NaNojcVYpt6Lw3xr4bcI1c6Tc0fjBlz5OkQMhZlLApg,16948
+matplotlib/tests/test_path.py,sha256=X6kEMiXLHVdRaZ9w2JF2ID2_DtIVonQDByvXpLbKXSo,12736
+matplotlib/tests/test_patheffects.py,sha256=I1C81Cms_QBK6cPg_xxKZBXKykZuGI5H2wwFtwnALZk,5207
+matplotlib/tests/test_pickle.py,sha256=BahI72bPhtSEylUvRwKgksK4N5i2vjsQ1APvEw3Om-0,5653
+matplotlib/tests/test_png.py,sha256=-ik0JJOAuZCED13VshP5RZsBOQpXu_Kv9I1oaZ_grxs,1705
+matplotlib/tests/test_preprocess_data.py,sha256=vVnEzVU-jd1hAQwh-b9IbWo-S_pIg4I9NCpSQ1lLChI,10034
+matplotlib/tests/test_pyplot.py,sha256=9KEPaGTyHduk7vi40kbva03HFk7bm8KrGlWjkBGMoQ0,1662
+matplotlib/tests/test_quiver.py,sha256=_dGa3bJmvNqcnBvFhGntvhBKRipROZUNOrSm-lMtc7Y,8087
+matplotlib/tests/test_rcparams.py,sha256=HnXA81intnBhogrXbCuaAfOV2nyvbGvOCESYElue6C4,20896
+matplotlib/tests/test_sankey.py,sha256=KdWhgZpEiCJ8o7yuFjnCgqFeodwKSCuuhV_mhIc04dM,310
+matplotlib/tests/test_scale.py,sha256=kolkvXNDsSm7P44yrVDNuz25lve4iGGreaG-JC8-VIA,5895
+matplotlib/tests/test_simplification.py,sha256=_dIImjiA0VHBEbOrGQluQKCS6SQFwEVvfL9pqIn_SHI,11048
+matplotlib/tests/test_skew.py,sha256=hngaWfqV6zqZSnRmVadBvtMIrg1snTZvu3fsqv5YddU,6310
+matplotlib/tests/test_sphinxext.py,sha256=VfFfCq5T3jaVN0Ut67SHpilUOzl-_zaJDnGxfGEtrgA,2022
+matplotlib/tests/test_spines.py,sha256=5jGy42khH7ndaHRCgJONXBOvM5ZnrzOhPSR42IAUAA0,3132
+matplotlib/tests/test_streamplot.py,sha256=_9iDNwQ0M1djl0eEX94QJtPYqCKFN5ncU_cIKJlm39w,3909
+matplotlib/tests/test_style.py,sha256=FKsTSWx43sls_PqZzOEJ-ZRaiBmSmVQCgCxfGTlyiNc,5677
+matplotlib/tests/test_subplots.py,sha256=RIk5cOGJmZwQz4UzZ_IEe1rzx4riAjO3bdltY5p1F6k,5992
+matplotlib/tests/test_table.py,sha256=UEvwlff7Jdx7ezPMc8nAdaCO0u11PzNpCZqYGLDS3O4,5685
+matplotlib/tests/test_testing.py,sha256=6pJ-SCQP1Bj4Qw5m5CEz2aWV4Bl7ovXE2uUpttiIu3Y,449
+matplotlib/tests/test_texmanager.py,sha256=zCtJ3JnZNfP2AQNy7q2LQAgaflSe7S5htJkJNylQSGE,459
+matplotlib/tests/test_text.py,sha256=49TKkDPy_tFRyxPLSp8-GgeS8TdU87JW24IUvA13NVM,20461
+matplotlib/tests/test_ticker.py,sha256=niDdQLrvTIUa4x0jWchca_LJJFQc7EQVfBopnF-DjEM,49453
+matplotlib/tests/test_tightlayout.py,sha256=Rg-Vw7VJSFxonhO5qnb2StPkMK7R4jpohMdFQbtPVSc,9685
+matplotlib/tests/test_transforms.py,sha256=1CO_STcfUU4yo4bJZLdTpJ7dF3E285LVbG-OBpFUchA,24892
+matplotlib/tests/test_triangulation.py,sha256=KWSRvg-_xE5Wz-_LAqB-EvI_5uy9isc9GTmhmi189Kw,45372
+matplotlib/tests/test_ttconv.py,sha256=w6U4-5OiEuXsOskvMz4E-f9iqPoxCt8dIC2TNB7PwXo,655
+matplotlib/tests/test_type1font.py,sha256=C0pCPBGOv49SR2xxDOq6LSXAEH_ZNvIWvr_jG-23Gmc,2097
+matplotlib/tests/test_units.py,sha256=DhE6O7PZhMUf4FMoc1ByTcMgpS9-gMmjqsiQnqBBTKc,5710
+matplotlib/tests/test_usetex.py,sha256=OrkN0Alth9lTFBWJbiQDsszhW70oO6TagLaD9XsCFmo,1245
+matplotlib/tests/test_widgets.py,sha256=ijPT0E0-nS1J3kBpxoWSok1GbqlZf22J5Csbto3nl4U,17654
+matplotlib/tests/tinypages/.gitignore,sha256=re9gqsLda7alFAVabOwnvrzS8ChI0uTxX1FxzsvqyEE,8
+matplotlib/tests/tinypages/README.md,sha256=l-sVwF8k46XxCZdwprn5wSibt6S03ICVLlMqJJN7EDo,124
+matplotlib/tests/tinypages/__pycache__/conf.cpython-38.pyc,,
+matplotlib/tests/tinypages/__pycache__/range4.cpython-38.pyc,,
+matplotlib/tests/tinypages/__pycache__/range6.cpython-38.pyc,,
+matplotlib/tests/tinypages/_static/.gitignore,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+matplotlib/tests/tinypages/_static/README.txt,sha256=1nnoizmUuHn5GKx8RL6MwJPlkyGmu_KHhYIMTDSWUNM,303
+matplotlib/tests/tinypages/conf.py,sha256=0_a4wyqPA9oaOFpLLpSEzkZI-hwtyRbqLWBx9nf0sLA,8432
+matplotlib/tests/tinypages/index.rst,sha256=kLSy7c3SoIAVsKOFkbzB4zFVzk3HW3d_rJHlHcNGBAg,446
+matplotlib/tests/tinypages/range4.py,sha256=fs2krzi9sY9ysmJRQCdGs_Jh1L9vDXDrNse7c0aX_Rw,81
+matplotlib/tests/tinypages/range6.py,sha256=a2EaHrNwXz4GJqhRuc7luqRpt7sqLKhTKeid9Drt2QQ,281
+matplotlib/tests/tinypages/some_plots.rst,sha256=C9rwV9UVlhFvxm8VqV6PoAP1AQ8Kk0LGZI9va4joif0,2156
+matplotlib/texmanager.py,sha256=kiFNpjY5S8OJ2Q6WPZ2h6YueL_zrXZQyIdrFgP_XRnI,17097
+matplotlib/text.py,sha256=bt04zgIbjJUOqdZ8gomc_zWF3UOYL5mHWaHb9OxxYHM,81090
+matplotlib/textpath.py,sha256=dEZ1Vg4ZUI5M4iTHn-7I45KeR1FTit78jQ-3EyK6orc,17010
+matplotlib/ticker.py,sha256=Dp_BzFOaTG0Qy7W1H4-yo5JlNNLMJS2L8BiXYjCC4Xk,101242
+matplotlib/tight_bbox.py,sha256=bQAOXPKfVWoBkGZbC0ckRcldgLJ8eBUe2IsT8UQpgaY,2590
+matplotlib/tight_layout.py,sha256=BrNob3eytUkmtXtwgfD3jdRgNh_tIjDE89AZjAnhFdY,14586
+matplotlib/transforms.py,sha256=cynqPdAO9gtYxmDBr_zr1VTSlIlbtOAwfNzGqsVV208,97572
+matplotlib/tri/__init__.py,sha256=XMaejh88uov7Neu7MuYMyaNQqaxg49nXaiJfvjifrRM,256
+matplotlib/tri/__pycache__/__init__.cpython-38.pyc,,
+matplotlib/tri/__pycache__/triangulation.cpython-38.pyc,,
+matplotlib/tri/__pycache__/tricontour.cpython-38.pyc,,
+matplotlib/tri/__pycache__/trifinder.cpython-38.pyc,,
+matplotlib/tri/__pycache__/triinterpolate.cpython-38.pyc,,
+matplotlib/tri/__pycache__/tripcolor.cpython-38.pyc,,
+matplotlib/tri/__pycache__/triplot.cpython-38.pyc,,
+matplotlib/tri/__pycache__/trirefine.cpython-38.pyc,,
+matplotlib/tri/__pycache__/tritools.cpython-38.pyc,,
+matplotlib/tri/triangulation.py,sha256=NCY1Fx66H3MHrMA1-NW6G_shopBtFu1Ehm1f6XyRDBE,8315
+matplotlib/tri/tricontour.py,sha256=4gKIAi1CFKWK8BCqAXMD6PlUKxnrC9GvzeQz0Yknvik,9794
+matplotlib/tri/trifinder.py,sha256=umsDJX2kach9MVDvEirLdN9q31QQ9tdP67U71eFyGj8,3497
+matplotlib/tri/triinterpolate.py,sha256=EP-ZwNC3HlOGB4jNaHGUCncXeO7LocvT2QhTr-jKCrk,64886
+matplotlib/tri/tripcolor.py,sha256=H6DEToUjfU12hDkfekshYyuQoZc7Fa4r4W6MOR5d6Jw,5169
+matplotlib/tri/triplot.py,sha256=aZ9O_VVLH0AOne31u11ltLlyVyhqKtyzec7WH3b3pkk,2857
+matplotlib/tri/trirefine.py,sha256=uwAminHKuxgE0ZuwwllIpqK2Ww_q--VKzMicWUslA0s,13765
+matplotlib/tri/tritools.py,sha256=BggcSXDVoc2mSyJp7wQ51qaw8WhdL7ianZ-D7ezkuC8,12398
+matplotlib/ttconv.cpython-38-x86_64-linux-gnu.so,sha256=1jdaSZnni-N5-Ec75mfmTXKMD54BQfFW0F_kdcL7ifE,83952
+matplotlib/type1font.py,sha256=-D1P81hhDUVNAHZ55-Df4cPc-3R8ycY_judVBZphgsk,12168
+matplotlib/units.py,sha256=dgSbQsrJPRiKf5IJv-PM0xYMcvT3kle3gdPJsdjbRZk,7332
+matplotlib/widgets.py,sha256=lMHpWCFdFTwSCEO5uL4fjcsoDQMzkzlWyVXnx80FVn4,94615
+mpl_toolkits/axes_grid/__init__.py,sha256=VLlc0DaOkr9JumPa8W4zt9lGHp180ie8_WLPZVNSJMw,537
+mpl_toolkits/axes_grid/__pycache__/__init__.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/anchored_artists.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/angle_helper.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axes_divider.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axes_grid.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axes_rgb.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axes_size.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axis_artist.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axisline_style.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/axislines.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/clip_path.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/colorbar.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/floating_axes.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/grid_finder.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/grid_helper_curvelinear.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/inset_locator.cpython-38.pyc,,
+mpl_toolkits/axes_grid/__pycache__/parasite_axes.cpython-38.pyc,,
+mpl_toolkits/axes_grid/anchored_artists.py,sha256=_F6-9iacZidb5JpJ8jCOZ9PdiZaR5qpfBjf-3VjTzNc,291
+mpl_toolkits/axes_grid/angle_helper.py,sha256=Tb4Mb_NGkUdkisebe2dqfBdFmUZiSmGyUnftiSeSIls,51
+mpl_toolkits/axes_grid/axes_divider.py,sha256=tJlPia3Z8xLq6uXehBwAlD_4ywMvRTTkM73qNnCpo7Q,178
+mpl_toolkits/axes_grid/axes_grid.py,sha256=UPlVDwsze_w2aZeLaMg4WZVK3q2EvWePXTFZFvjCQz4,89
+mpl_toolkits/axes_grid/axes_rgb.py,sha256=d3h2tImoPxvVtl8i4IBA_i1vBQykZDYizcNDGdjRltE,201
+mpl_toolkits/axes_grid/axes_size.py,sha256=v4Nhxe7DVp1FkKX03DqJJ1aevDanDvgKT9r0ouDzTxw,48
+mpl_toolkits/axes_grid/axis_artist.py,sha256=zUlJFUHueDsMtzLi_mK2_Wf-nSBQgiTsMOFpo_SngZ0,50
+mpl_toolkits/axes_grid/axisline_style.py,sha256=lNVHXkFWhSWPXOOfF-wlVkDPzmzuStJyJzF-NS5Wf_g,53
+mpl_toolkits/axes_grid/axislines.py,sha256=kVyhb6laiImmuNE53QTQh3kgxz0sO1mcSMpnqIdjylA,48
+mpl_toolkits/axes_grid/clip_path.py,sha256=s-d36hUiy9I9BSr9wpxjgoAACCQrczHjw072JvArNvE,48
+mpl_toolkits/axes_grid/colorbar.py,sha256=DckRf6tadLeTNjx-Zk1u3agnSGZgizDjd0Dxw1-GRdw,171
+mpl_toolkits/axes_grid/floating_axes.py,sha256=i35OfV1ZMF-DkLo4bKmzFZP6LgCwXfdDKxYlGqjyKOM,52
+mpl_toolkits/axes_grid/grid_finder.py,sha256=Y221c-Jh_AFd3Oolzvr0B1Zrz9MoXPatUABQdLsFdpw,50
+mpl_toolkits/axes_grid/grid_helper_curvelinear.py,sha256=nRl_B-755X7UpVqqdwkqc_IwiTmM48z3eOMHuvJT5HI,62
+mpl_toolkits/axes_grid/inset_locator.py,sha256=qqXlT8JWokP0kV-8NHknZDINtK-jbXfkutH_1tcRe_o,216
+mpl_toolkits/axes_grid/parasite_axes.py,sha256=kCFtaRTd0O8ePL78GOYvhEKqn8rE9bk61v0kVgMb6UE,469
+mpl_toolkits/axes_grid1/__init__.py,sha256=-lw0ZfG4XUpuAolCpXKFwtS3w1LJ1ZToSEC9OSmB-4Q,204
+mpl_toolkits/axes_grid1/__pycache__/__init__.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/anchored_artists.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/axes_divider.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/axes_grid.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/axes_rgb.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/axes_size.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/colorbar.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/inset_locator.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/mpl_axes.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/__pycache__/parasite_axes.cpython-38.pyc,,
+mpl_toolkits/axes_grid1/anchored_artists.py,sha256=F9396ifiWLedXEsivC3-rJjhYOxZ84dHYFhlRMVt4wE,21031
+mpl_toolkits/axes_grid1/axes_divider.py,sha256=rZOhnowUzJ3mXb6LKkLPjDyxkz-EPJmA3MCafFa8VN8,29175
+mpl_toolkits/axes_grid1/axes_grid.py,sha256=2ThJGeKXb9avu0KN3ujBliY2HV26IHC1daxy74t8ITk,27888
+mpl_toolkits/axes_grid1/axes_rgb.py,sha256=NsMKkaMEeXd6N1AYVP1Pz-ulwNeof1pU4qkReoQ8xFc,6652
+mpl_toolkits/axes_grid1/axes_size.py,sha256=YBC1PUmUK1-0c7cuqgJQBpDwVrbZQaWXyWadvuOO5bs,8602
+mpl_toolkits/axes_grid1/colorbar.py,sha256=Y0-Uf9osbz31YBOTxxs_UFXbp6Xyc9Rais47tBMjTXI,27123
+mpl_toolkits/axes_grid1/inset_locator.py,sha256=0BA8z3BiT78sS2-ksVe8iKS6KLxVv_NTV38NTLmRuu4,23675
+mpl_toolkits/axes_grid1/mpl_axes.py,sha256=MJVYUN4YRtTWrq1wmyv_y61O002tiyFESmBSaJ8xkG4,4380
+mpl_toolkits/axes_grid1/parasite_axes.py,sha256=ug0YRJiTtA3YVikdKMGj-qjoeOFnqNw2QnRab0Qp7wI,13570
+mpl_toolkits/axisartist/__init__.py,sha256=2zsgjqTtP_NXv78MEaKabmfmkjA7yhy77pIcaR57YWs,748
+mpl_toolkits/axisartist/__pycache__/__init__.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/angle_helper.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/axes_divider.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/axes_grid.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/axes_rgb.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/axis_artist.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/axisline_style.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/axislines.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/clip_path.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/floating_axes.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/grid_finder.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/grid_helper_curvelinear.cpython-38.pyc,,
+mpl_toolkits/axisartist/__pycache__/parasite_axes.cpython-38.pyc,,
+mpl_toolkits/axisartist/angle_helper.py,sha256=KZiXfi0IPcGp6JFXewj0VLrUycSfI93ezoimkti3wpY,12632
+mpl_toolkits/axisartist/axes_divider.py,sha256=baPCBjM20SvAUeMjhvlS_cccRSM1y7ZKybtoW8upo2k,127
+mpl_toolkits/axisartist/axes_grid.py,sha256=vfd_EXHuYQ7iIVK2FOm6inLhb7huZxtOSvFyOVW2GmU,610
+mpl_toolkits/axisartist/axes_rgb.py,sha256=TpJCB8eA0wHZVXOxxfFoy1Tk_KFj68sZvo74doDeHYE,179
+mpl_toolkits/axisartist/axis_artist.py,sha256=gScMYbfAyxT5IgyEZRVto2NyZVLGpDQeJ1t9tk1d3OM,43453
+mpl_toolkits/axisartist/axisline_style.py,sha256=bQ3M5gAxS7HbCC3oOQgrSyNWdk_FbvXfX24Eatge0UE,5098
+mpl_toolkits/axisartist/axislines.py,sha256=mrQIihqbfBi2F-QgLRjZvHGM6rEojStBzojYpVNkjg0,20852
+mpl_toolkits/axisartist/clip_path.py,sha256=LE_IIP0byNr5ELJlD8_8fsAh215MUDoK19-BISuFB80,3777
+mpl_toolkits/axisartist/floating_axes.py,sha256=xSAJ5Myaa5Xm9DkOhJcdaPwzdPyvJnw5K9YPmDwB5ME,13122
+mpl_toolkits/axisartist/grid_finder.py,sha256=K1Gae38Jt2OFfScZaKbxkLLcWrHDm-2bAVsl56SC1F0,10388
+mpl_toolkits/axisartist/grid_helper_curvelinear.py,sha256=6QT9nTQ2dJ_NPsazS0Q0hC8W5IV7yzqYd_Fa8_gJXmI,14384
+mpl_toolkits/axisartist/parasite_axes.py,sha256=1sQwBEYuXHpaEeObb7cXh0I1xWroYtcvFiEmwrzqK3w,447
+mpl_toolkits/mplot3d/__init__.py,sha256=V2iPIP9VyRhoJsFWnQf5AkfyI1GSSP9H6hICEe9edJo,27
+mpl_toolkits/mplot3d/__pycache__/__init__.cpython-38.pyc,,
+mpl_toolkits/mplot3d/__pycache__/art3d.cpython-38.pyc,,
+mpl_toolkits/mplot3d/__pycache__/axes3d.cpython-38.pyc,,
+mpl_toolkits/mplot3d/__pycache__/axis3d.cpython-38.pyc,,
+mpl_toolkits/mplot3d/__pycache__/proj3d.cpython-38.pyc,,
+mpl_toolkits/mplot3d/art3d.py,sha256=MAR_fFyBwe3VkwlOeWMccwDWo8RR1VqhUjuipLPuIFU,27647
+mpl_toolkits/mplot3d/axes3d.py,sha256=iJ7LN-UKY7xMaGjYbeu-J1IuC54QX3irx7Kkh5gInzY,98609
+mpl_toolkits/mplot3d/axis3d.py,sha256=UdxPxUW7GDoOpBy4uByLmLZJfle2wu0oYGenDHjjcLA,16737
+mpl_toolkits/mplot3d/proj3d.py,sha256=5xwjogevdUCBaV9sx1RlNqO6fMCSIBi_1_uVZniy2pU,5499
+mpl_toolkits/tests/__init__.py,sha256=iPdasxJf0vpIi11tQ98OVSQgS0UaPUyOEGGfAryAhIA,381
+mpl_toolkits/tests/__pycache__/__init__.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/conftest.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axes_grid.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axes_grid1.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_angle_helper.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_axis_artist.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_axislines.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_clip_path.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_floating_axes.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_grid_finder.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_axisartist_grid_helper_curvelinear.cpython-38.pyc,,
+mpl_toolkits/tests/__pycache__/test_mplot3d.cpython-38.pyc,,
+mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png,sha256=yvo6erXXc3Z9aO0rrEezBooCc6KhAw7wKv4WngOQmFA,87393
+mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png,sha256=XMZGgG7_9k96bKhI2G--XBVKpct5O5psbGH2Wvj5YA0,10784
+mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png,sha256=fkPsdmhd4S1g-QxMb55M63iAgWmC2G4ytcLOT9tMAD0,11039
+mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.pdf,sha256=eW2CuM_T4d95dC-DU0PmmQD7gqRQIO0rcQpvp-zu1i4,25446
+mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.png,sha256=VfRfs6p4akgjGxxNm6Bu83Pg0v1KmU7WPu97_-kzNFc,48825
+mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.svg,sha256=usfsa3y-s-N2KMOzsOZHTq-PZXgAPXsSM-lkxJ3ZUi0,172812
+mpl_toolkits/tests/baseline_images/test_axes_grid1/fill_facecolor.png,sha256=Tkrylxebxm8SuWZjQK0qXSX8m9QsQU6kYm7L2dgt4yg,14845
+mpl_toolkits/tests/baseline_images/test_axes_grid1/image_grid.png,sha256=HIg43mbdOUyEWY-jQ1DEpG7DMqCcWbX1Xf2itmW1YL4,3786
+mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_axes.png,sha256=RQmR39E6Vskvl7G4LInHibW9E1VK0QgCvI-hBlb-E2E,9928
+mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_locator.png,sha256=bQKKKUuoU_EZwZT_9FzzeVKsKwUUBOZV55g4vVUbnCU,9490
+mpl_toolkits/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png,sha256=rvglsLg8Kl9jE_JukTJ5B3EHozsIYJsaYA0JIOicZL8,25997
+mpl_toolkits/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png,sha256=0YzkFhxs4SBG_FEmnWB10bXIxl9aq7WJveQAqHm0JrQ,37701
+mpl_toolkits/tests/baseline_images/test_axes_grid1/zoomed_axes.png,sha256=mUu8zJtz8FMb7h5l4Cfp3oBi9jaNR5OoyaDgwqpAZp4,25893
+mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist.png,sha256=qdlk9UPScCAN9RBOhoNqLmJvmkXt8pCuwuQtrz5E8Bs,10151
+mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_labelbase.png,sha256=An5lovtvAiNA1NZI-E8kOj6eYTruQMqwf3J7pXwdk4A,10598
+mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticklabels.png,sha256=7vuAKkIqcpgJrc2AF7oslf-E_sDfSlCoymyc87u4AWs,5696
+mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticks.png,sha256=CkVtCWG13ViW0w2DsbzfXSvoFWHYaaqQYeEYpbKbOg8,5695
+mpl_toolkits/tests/baseline_images/test_axisartist_axislines/ParasiteAxesAuxTrans_meshplot.png,sha256=FOgl-Glmzhdp6V8mz4StofTsFXGysFkEcUeaWtmJDZs,34354
+mpl_toolkits/tests/baseline_images/test_axisartist_axislines/Subplot.png,sha256=tRpYCjR5zUkafA85DVmY3duTEouwCZq6jDwSF4UsBS8,26919
+mpl_toolkits/tests/baseline_images/test_axisartist_axislines/SubplotZero.png,sha256=3kCrz7HQMYrK3iDgYgf8kyigxRtIGFBbcUzJPtiXh_E,28682
+mpl_toolkits/tests/baseline_images/test_axisartist_clip_path/clip_path.png,sha256=BtMyb7ZawcgId9jl1_qW72lU_ZyxLN780uQ9bCLjbHA,25701
+mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear3.png,sha256=4th7Y74_9YV6X25RqJW0Op9WDzGRCcxF1kfNogkgozE,52835
+mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear4.png,sha256=cYjrSiH6Mvor-VhmwNUgX7Le3_k1rurpd8L5vhTf16s,29374
+mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/axis_direction.png,sha256=3fue92dg-ntYI0XX0nB31IFpgRT2V3izqjrmLvEdYN4,40536
+mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/custom_transform.png,sha256=4cQhIFK1z8oPUVyvkHNZ_m-GCbikmUbTvkvYVGy6U4o,15118
+mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/polar_box.png,sha256=wWaPM3I7_435SkVQqIggb8BHrWBMWrsSVyMZQQJ6fE4,62526
+mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_cla.png,sha256=htnP1CA8dd85KqdnOsHVlsehT90MUoQD8kFTyra0AuE,51409
+mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_labelpad.png,sha256=zrLsk8t7s970yaY3cqj6SOMbI6UY8Loe0Zbp0WqFtwQ,66817
+mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png,sha256=SoyN30SsuvEECZyB_ReGP3ZKGZJazOp05dXa3YUn7Jc,47796
+mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png,sha256=Qw909B4nDmV9DlMuo1DKk7y5ndjtvni5d_DcysmG9VA,100466
+mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png,sha256=soaHKytaVZHmPvHIEcPFQDJDqhEEuNO_JIVCZyjacxM,66294
+mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png,sha256=laBssZyuviouRe3seffJWOz45OLunXC0dFHi-lYge1w,115939
+mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png,sha256=tii1IakS8MC_Vuwd95HhcyM0iq4zGN5DxgRFfB9mKu8,83161
+mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png,sha256=Jb-fhAcgogE8jn9DSsaqInUfWC7D_5Pf3QRf7XWAX2Q,42575
+mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png,sha256=dE8eHoj43eePB44F1nLM2RLj8iqw8rCYI3D0VD3gUg0,39694
+mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png,sha256=DQT-NruHCeG5LKpjG-dlLln3aCoPKhua5PQnHTafBGU,60217
+mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png,sha256=iHxYbxRflxIpjoAtWo9KAvgK4CS-k4N03p0SX_xF4DA,39674
+mpl_toolkits/tests/baseline_images/test_mplot3d/plot_3d_from_2d.png,sha256=AWos5EJWMerD0tgVZyvBofz-5hnCq6fhGHKmQi-izAg,56593
+mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_alpha.png,sha256=AnZJbnnBnF_fny5FBTlSWblkNMlPI1dcQRlCfGPIjWI,52046
+mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png,sha256=ePzSA-iFaQbmH603vw1jhs9vyIt45xXnbpIuUF3a1l8,52065
+mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube.png,sha256=AJ0EoayvdBoywpOUWcxbMQ0oB7cTzcoWGgGyx2qgQMU,23182
+mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png,sha256=5Phz7BclSciZpg4SDu-eUQ-v_ikHbEqReQWCdeHywQk,16210
+mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_lines_dists.png,sha256=XCd4hX2ckc5GCxcgenkRJ8MT7pX-3iMLylD2rCjNl-4,18898
+mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png,sha256=PBllNI1kHf1rz-oPK507OwsPNE9DPwivXAVJM9DemBI,104755
+mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.png,sha256=98D3k5QIL7KugUwzqJhdLtp9dgDGgx8hGa9_u8cvX6o,37954
+mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png,sha256=67yp7-6f-vDiYTmCqMFfuIEGly5UHCCUOV84YJtLsX8,80392
+mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_middle.png,sha256=N4o26wMzfnyxndPbZ2VnsjIAiNYrFN9Aa40ficwO9AM,104735
+mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_tail.png,sha256=Ff_UrWxD-VIMQLN1uXy5u_Yd5e1P427YfGM05nvU2kE,104951
+mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png,sha256=MDaocusHz6Itinjm2j6fnDh-rl1fqVjnqM89nP8bwZs,43155
+mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png,sha256=Y7De9BIFLp0Ova4fk9IcXloNjiwmifTrFA1IfVJA3aE,41598
+mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png,sha256=Ok0UmO2DELze2yK8mRx0CifmRAgvjyS1IvERsBRvFlU,54712
+mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png,sha256=kWYGPWgG1ZrQVgd389xmIZ1cc59tAkKKiikKaJzHKkw,43474
+mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png,sha256=sO68K3cti2YsPkkjEIAvc7_pd8JaHpc_a78UVx4Htu4,78758
+mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png,sha256=8IjYmJP6cBhnPGLz-WDyn7UUMYZ10Kz2MpjOFwDUVow,71328
+mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png,sha256=nO0gJBIluLEX3mlxXY3C6bx-9Jf_xJyXAnTXKnqrIkQ,99103
+mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png,sha256=LSVF3lI7JnYXmCBAcn410k3JRE-3ssp84Dmgg3zr0FA,94328
+mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png,sha256=LELjsQnfvfzLF8rNSh5azv9BdwF8TlCMn2wbBJNTtyQ,178141
+mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png,sha256=UJXpFMSWIFgUH2rLEv1nxDvHrnshlSz4d5ZB34upa3g,65759
+mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png,sha256=J8m66Bc5NoeZxGRwbBThV06_aybNvMeTtwUnmCRzNak,93580
+mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png,sha256=5s02No0RWv8NYV_ccFUnDdBaUHQ8DTO90qddKBnN6mw,131458
+mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png,sha256=7VCcyzKKKz8E7txQV4wi-jEbtftaFW_ekWmA9E1qx3Y,60304
+mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png,sha256=KwDmkuK7FMtl0Q2ank7wcph9uTncUr7UdTh2hUYdZP8,121446
+mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png,sha256=epmsR4rWGzh7prW3RL_t8ZcUEsphM5bc0t5j__iauOY,108371
+mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerocstride.png,sha256=WaO_3NcaZPFzlui5SQYJ-TbUylHkSbieneCYPffNgAA,81117
+mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerorstride.png,sha256=y1JvfuVOBiNhJoJ2HdOXyBYBBkQm-oaPcoekfT-cMso,84284
+mpl_toolkits/tests/conftest.py,sha256=Ph6QZKdfAnkPwU52StddC-uwtCHfANKX1dDXgtX122g,213
+mpl_toolkits/tests/test_axes_grid.py,sha256=SUnj9aImCEI-Q3_cM8e5BlizoNle9E1joKkE_q57OAg,2694
+mpl_toolkits/tests/test_axes_grid1.py,sha256=pUw19PTds6WRlLON5U9ZWmt6HxV56Q6DKDPVGM34ysk,17113
+mpl_toolkits/tests/test_axisartist_angle_helper.py,sha256=PwhJwBm2kk4uMyhdO5arQs8IlqSX2vN0hvUzI7YHqrw,5670
+mpl_toolkits/tests/test_axisartist_axis_artist.py,sha256=N4Khx8jSxkoiMz3KvumodmFKHZUtdwtjkzxLWPSdyuw,3008
+mpl_toolkits/tests/test_axisartist_axislines.py,sha256=4ujhndnDq-6albE4WwVFTVURfjG1xK1597smUGMxfFg,2331
+mpl_toolkits/tests/test_axisartist_clip_path.py,sha256=afS3nvNqCgvDpJdg_MvbwydtSWv5b6ciP-Iq2aNcNFQ,1004
+mpl_toolkits/tests/test_axisartist_floating_axes.py,sha256=xENnUpFU8EHPgnON6W1xqMVWIq8qxIzuGf1oMmSMFJo,4127
+mpl_toolkits/tests/test_axisartist_grid_finder.py,sha256=e65sLudWFIXeU08Sis3_SI1JEI6eq8YqKj-80F_Nohk,325
+mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py,sha256=0AT6TdbxHGHlircgjNZyK2u1dw05YLXdOhLqXmsKyw4,7572
+mpl_toolkits/tests/test_mplot3d.py,sha256=TojtKh1gbRWl-kCWQcYaFMOS89txDRd02O0NBJuu-vE,31991
+pylab.py,sha256=u_By3CHla-rBMg57egFXIxZ3P_J6zEkSu_dNpBcH5pw,90

+ 5 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.31.1)
+Root-Is-Purelib: false
+Tag: cp38-cp38-manylinux1_x86_64
+

+ 1 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/namespace_packages.txt

@@ -0,0 +1 @@
+mpl_toolkits

+ 3 - 0
venv/lib/python3.8/site-packages/matplotlib-3.2.1.dist-info/top_level.txt

@@ -0,0 +1,3 @@
+matplotlib
+mpl_toolkits
+pylab

BIN
venv/lib/python3.8/site-packages/matplotlib/.libs/libpng16-cfdb1654.so.16.21.0


BIN
venv/lib/python3.8/site-packages/matplotlib/.libs/libz-a147dcb0.so.1.2.3


+ 1601 - 0
venv/lib/python3.8/site-packages/matplotlib/__init__.py

@@ -0,0 +1,1601 @@
+"""
+This is an object-oriented plotting library.
+
+A procedural interface is provided by the companion pyplot module,
+which may be imported directly, e.g.::
+
+    import matplotlib.pyplot as plt
+
+or using ipython::
+
+    ipython
+
+at your terminal, followed by::
+
+    In [1]: %matplotlib
+    In [2]: import matplotlib.pyplot as plt
+
+at the ipython shell prompt.
+
+For the most part, direct use of the object-oriented library is
+encouraged when programming; pyplot is primarily for working
+interactively.  The
+exceptions are the pyplot commands :func:`~matplotlib.pyplot.figure`,
+:func:`~matplotlib.pyplot.subplot`,
+:func:`~matplotlib.pyplot.subplots`, and
+:func:`~pyplot.savefig`, which can greatly simplify scripting.
+
+Modules include:
+
+    :mod:`matplotlib.axes`
+        defines the :class:`~matplotlib.axes.Axes` class.  Most pyplot
+        commands are wrappers for :class:`~matplotlib.axes.Axes`
+        methods.  The axes module is the highest level of OO access to
+        the library.
+
+    :mod:`matplotlib.figure`
+        defines the :class:`~matplotlib.figure.Figure` class.
+
+    :mod:`matplotlib.artist`
+        defines the :class:`~matplotlib.artist.Artist` base class for
+        all classes that draw things.
+
+    :mod:`matplotlib.lines`
+        defines the :class:`~matplotlib.lines.Line2D` class for
+        drawing lines and markers
+
+    :mod:`matplotlib.patches`
+        defines classes for drawing polygons
+
+    :mod:`matplotlib.text`
+        defines the :class:`~matplotlib.text.Text`,
+        :class:`~matplotlib.text.TextWithDash`, and
+        :class:`~matplotlib.text.Annotate` classes
+
+    :mod:`matplotlib.image`
+        defines the :class:`~matplotlib.image.AxesImage` and
+        :class:`~matplotlib.image.FigureImage` classes
+
+    :mod:`matplotlib.collections`
+        classes for efficient drawing of groups of lines or polygons
+
+    :mod:`matplotlib.colors`
+        classes for interpreting color specifications and for making
+        colormaps
+
+    :mod:`matplotlib.cm`
+        colormaps and the :class:`~matplotlib.image.ScalarMappable`
+        mixin class for providing color mapping functionality to other
+        classes
+
+    :mod:`matplotlib.ticker`
+        classes for calculating tick mark locations and for formatting
+        tick labels
+
+    :mod:`matplotlib.backends`
+        a subpackage with modules for various gui libraries and output
+        formats
+
+The base matplotlib namespace includes:
+
+    :data:`~matplotlib.rcParams`
+        a global dictionary of default configuration settings.  It is
+        initialized by code which may be overridden by a matplotlibrc
+        file.
+
+    :func:`~matplotlib.rc`
+        a function for setting groups of rcParams values
+
+    :func:`~matplotlib.use`
+        a function for setting the matplotlib backend.  If used, this
+        function must be called immediately after importing matplotlib
+        for the first time.  In particular, it must be called
+        **before** importing pyplot (if pyplot is imported).
+
+matplotlib was initially written by John D. Hunter (1968-2012) and is now
+developed and maintained by a host of others.
+
+Occasionally the internal documentation (python docstrings) will refer
+to MATLAB&reg;, a registered trademark of The MathWorks, Inc.
+
+"""
+# NOTE: This file must remain Python 2 compatible for the foreseeable future,
+# to ensure that we error out properly for existing editable installs.
+
+import sys
+if sys.version_info < (3, 5):  # noqa: E402
+    raise ImportError("""
+Matplotlib 3.0+ does not support Python 2.x, 3.0, 3.1, 3.2, 3.3, or 3.4.
+Beginning with Matplotlib 3.0, Python 3.5 and above is required.
+
+See Matplotlib `INSTALL.rst` file for more information:
+
+    https://github.com/matplotlib/matplotlib/blob/master/INSTALL.rst
+
+""")
+
+import atexit
+from collections import namedtuple
+from collections.abc import MutableMapping
+import contextlib
+from distutils.version import LooseVersion
+import functools
+import importlib
+import inspect
+from inspect import Parameter
+import locale
+import logging
+import os
+from pathlib import Path
+import pprint
+import re
+import shutil
+import subprocess
+import tempfile
+import warnings
+
+# cbook must import matplotlib only within function
+# definitions, so it is safe to import from it here.
+from . import cbook, rcsetup
+from matplotlib.cbook import (
+    MatplotlibDeprecationWarning, dedent, get_label, sanitize_sequence)
+from matplotlib.cbook import mplDeprecation  # deprecated
+from matplotlib.rcsetup import defaultParams, validate_backend, cycler
+
+import numpy
+
+# Get the version from the _version.py versioneer file. For a git checkout,
+# this is computed based on the number of commits since the last tag.
+from ._version import get_versions
+__version__ = str(get_versions()['version'])
+del get_versions
+
+_log = logging.getLogger(__name__)
+
+__bibtex__ = r"""@Article{Hunter:2007,
+  Author    = {Hunter, J. D.},
+  Title     = {Matplotlib: A 2D graphics environment},
+  Journal   = {Computing in Science \& Engineering},
+  Volume    = {9},
+  Number    = {3},
+  Pages     = {90--95},
+  abstract  = {Matplotlib is a 2D graphics package used for Python
+  for application development, interactive scripting, and
+  publication-quality image generation across user
+  interfaces and operating systems.},
+  publisher = {IEEE COMPUTER SOC},
+  year      = 2007
+}"""
+
+
+@cbook.deprecated("3.2")
+def compare_versions(a, b):
+    "Return whether version *a* is greater than or equal to version *b*."
+    if isinstance(a, bytes):
+        cbook.warn_deprecated(
+            "3.0", message="compare_versions arguments should be strs.")
+        a = a.decode('ascii')
+    if isinstance(b, bytes):
+        cbook.warn_deprecated(
+            "3.0", message="compare_versions arguments should be strs.")
+        b = b.decode('ascii')
+    if a:
+        return LooseVersion(a) >= LooseVersion(b)
+    else:
+        return False
+
+
+def _check_versions():
+
+    # Quickfix to ensure Microsoft Visual C++ redistributable
+    # DLLs are loaded before importing kiwisolver
+    from . import ft2font
+
+    for modname, minver in [
+            ("cycler", "0.10"),
+            ("dateutil", "2.1"),
+            ("kiwisolver", "1.0.1"),
+            ("numpy", "1.11"),
+            ("pyparsing", "2.0.1"),
+    ]:
+        module = importlib.import_module(modname)
+        if LooseVersion(module.__version__) < minver:
+            raise ImportError("Matplotlib requires {}>={}; you have {}"
+                              .format(modname, minver, module.__version__))
+
+
+_check_versions()
+
+
+if not hasattr(sys, 'argv'):  # for modpython
+    sys.argv = ['modpython']
+
+
+# The decorator ensures this always returns the same handler (and it is only
+# attached once).
+@functools.lru_cache()
+def _ensure_handler():
+    """
+    The first time this function is called, attach a `StreamHandler` using the
+    same format as `logging.basicConfig` to the Matplotlib root logger.
+
+    Return this handler every time this function is called.
+    """
+    handler = logging.StreamHandler()
+    handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
+    _log.addHandler(handler)
+    return handler
+
+
+def set_loglevel(level):
+    """
+    Sets the Matplotlib's root logger and root logger handler level, creating
+    the handler if it does not exist yet.
+
+    Typically, one should call ``set_loglevel("info")`` or
+    ``set_loglevel("debug")`` to get additional debugging information.
+
+    Parameters
+    ----------
+    level : {"notset", "debug", "info", "warning", "error", "critical"}
+        The log level of the handler.
+
+    Notes
+    -----
+    The first time this function is called, an additional handler is attached
+    to Matplotlib's root handler; this handler is reused every time and this
+    function simply manipulates the logger and handler's level.
+    """
+    _log.setLevel(level.upper())
+    _ensure_handler().setLevel(level.upper())
+
+
+def _logged_cached(fmt, func=None):
+    """
+    Decorator that logs a function's return value, and memoizes that value.
+
+    After ::
+
+        @_logged_cached(fmt)
+        def func(): ...
+
+    the first call to *func* will log its return value at the DEBUG level using
+    %-format string *fmt*, and memoize it; later calls to *func* will directly
+    return that value.
+    """
+    if func is None:  # Return the actual decorator.
+        return functools.partial(_logged_cached, fmt)
+
+    called = False
+    ret = None
+
+    @functools.wraps(func)
+    def wrapper(**kwargs):
+        nonlocal called, ret
+        if not called:
+            ret = func(**kwargs)
+            called = True
+            _log.debug(fmt, ret)
+        return ret
+
+    return wrapper
+
+
+_ExecInfo = namedtuple("_ExecInfo", "executable version")
+
+
+class ExecutableNotFoundError(FileNotFoundError):
+    """
+    Error raised when an executable that Matplotlib optionally
+    depends on can't be found.
+    """
+    pass
+
+
+@functools.lru_cache()
+def _get_executable_info(name):
+    """
+    Get the version of some executable that Matplotlib optionally depends on.
+
+    .. warning:
+       The list of executables that this function supports is set according to
+       Matplotlib's internal needs, and may change without notice.
+
+    Parameters
+    ----------
+    name : str
+        The executable to query.  The following values are currently supported:
+        "dvipng", "gs", "inkscape", "magick", "pdftops".  This list is subject
+        to change without notice.
+
+    Returns
+    -------
+    If the executable is found, a namedtuple with fields ``executable`` (`str`)
+    and ``version`` (`distutils.version.LooseVersion`, or ``None`` if the
+    version cannot be determined).
+
+    Raises
+    ------
+    ExecutableNotFoundError
+        If the executable is not found or older than the oldest version
+        supported by Matplotlib.
+    ValueError
+        If the executable is not one that we know how to query.
+    """
+
+    def impl(args, regex, min_ver=None, ignore_exit_code=False):
+        # Execute the subprocess specified by args; capture stdout and stderr.
+        # Search for a regex match in the output; if the match succeeds, the
+        # first group of the match is the version.
+        # Return an _ExecInfo if the executable exists, and has a version of
+        # at least min_ver (if set); else, raise ExecutableNotFoundError.
+        try:
+            output = subprocess.check_output(
+                args, stderr=subprocess.STDOUT,
+                universal_newlines=True, errors="replace")
+        except subprocess.CalledProcessError as _cpe:
+            if ignore_exit_code:
+                output = _cpe.output
+            else:
+                raise ExecutableNotFoundError(str(_cpe)) from _cpe
+        except OSError as _ose:
+            raise ExecutableNotFoundError(str(_ose)) from _ose
+        match = re.search(regex, output)
+        if match:
+            version = LooseVersion(match.group(1))
+            if min_ver is not None and version < min_ver:
+                raise ExecutableNotFoundError(
+                    f"You have {args[0]} version {version} but the minimum "
+                    f"version supported by Matplotlib is {min_ver}")
+            return _ExecInfo(args[0], version)
+        else:
+            raise ExecutableNotFoundError(
+                f"Failed to determine the version of {args[0]} from "
+                f"{' '.join(args)}, which output {output}")
+
+    if name == "dvipng":
+        return impl(["dvipng", "-version"], "(?m)^dvipng(?: .*)? (.+)", "1.6")
+    elif name == "gs":
+        execs = (["gswin32c", "gswin64c", "mgs", "gs"]  # "mgs" for miktex.
+                 if sys.platform == "win32" else
+                 ["gs"])
+        for e in execs:
+            try:
+                return impl([e, "--version"], "(.*)", "9")
+            except ExecutableNotFoundError:
+                pass
+        message = "Failed to find a Ghostscript installation"
+        raise ExecutableNotFoundError(message)
+    elif name == "inkscape":
+        info = impl(["inkscape", "-V"], "^Inkscape ([^ ]*)")
+        if info and info.version >= "1.0":
+            raise ExecutableNotFoundError(
+                f"You have Inkscape version {info.version} but Matplotlib "
+                f"only supports Inkscape<1.0")
+        return info
+    elif name == "magick":
+        path = None
+        if sys.platform == "win32":
+            # Check the registry to avoid confusing ImageMagick's convert with
+            # Windows's builtin convert.exe.
+            import winreg
+            binpath = ""
+            for flag in [0, winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY]:
+                try:
+                    with winreg.OpenKeyEx(
+                            winreg.HKEY_LOCAL_MACHINE,
+                            r"Software\Imagemagick\Current",
+                            0, winreg.KEY_QUERY_VALUE | flag) as hkey:
+                        binpath = winreg.QueryValueEx(hkey, "BinPath")[0]
+                except OSError:
+                    pass
+            if binpath:
+                for name in ["convert.exe", "magick.exe"]:
+                    candidate = Path(binpath, name)
+                    if candidate.exists():
+                        path = str(candidate)
+                        break
+        else:
+            path = "convert"
+        if path is None:
+            raise ExecutableNotFoundError(
+                "Failed to find an ImageMagick installation")
+        return impl([path, "--version"], r"^Version: ImageMagick (\S*)")
+    elif name == "pdftops":
+        info = impl(["pdftops", "-v"], "^pdftops version (.*)",
+                    ignore_exit_code=True)
+        if info and not ("3.0" <= info.version
+                         # poppler version numbers.
+                         or "0.9" <= info.version <= "1.0"):
+            raise ExecutableNotFoundError(
+                f"You have pdftops version {info.version} but the minimum "
+                f"version supported by Matplotlib is 3.0")
+        return info
+    else:
+        raise ValueError("Unknown executable: {!r}".format(name))
+
+
+@cbook.deprecated("3.1")
+def checkdep_dvipng():
+    try:
+        s = subprocess.Popen(['dvipng', '-version'],
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        stdout, stderr = s.communicate()
+        line = stdout.decode('ascii').split('\n')[1]
+        v = line.split()[-1]
+        return v
+    except (IndexError, ValueError, OSError):
+        return None
+
+
+@cbook.deprecated("3.1")
+def checkdep_ghostscript():
+    if checkdep_ghostscript.executable is None:
+        if sys.platform == 'win32':
+            # mgs is the name in miktex
+            gs_execs = ['gswin32c', 'gswin64c', 'mgs', 'gs']
+        else:
+            gs_execs = ['gs']
+        for gs_exec in gs_execs:
+            try:
+                s = subprocess.Popen(
+                    [gs_exec, '--version'], stdout=subprocess.PIPE,
+                    stderr=subprocess.PIPE)
+                stdout, stderr = s.communicate()
+                if s.returncode == 0:
+                    v = stdout[:-1].decode('ascii')
+                    if compare_versions(v, '9.0'):
+                        checkdep_ghostscript.executable = gs_exec
+                        checkdep_ghostscript.version = v
+            except (IndexError, ValueError, OSError):
+                pass
+    return checkdep_ghostscript.executable, checkdep_ghostscript.version
+checkdep_ghostscript.executable = None
+checkdep_ghostscript.version = None
+
+
+@cbook.deprecated("3.1")
+def checkdep_pdftops():
+    try:
+        s = subprocess.Popen(['pdftops', '-v'], stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        stdout, stderr = s.communicate()
+        lines = stderr.decode('ascii').split('\n')
+        for line in lines:
+            if 'version' in line:
+                v = line.split()[-1]
+        return v
+    except (IndexError, ValueError, UnboundLocalError, OSError):
+        return None
+
+
+@cbook.deprecated("3.1")
+def checkdep_inkscape():
+    if checkdep_inkscape.version is None:
+        try:
+            s = subprocess.Popen(['inkscape', '-V'],
+                                 stdout=subprocess.PIPE,
+                                 stderr=subprocess.PIPE)
+            stdout, stderr = s.communicate()
+            lines = stdout.decode('ascii').split('\n')
+            for line in lines:
+                if 'Inkscape' in line:
+                    v = line.split()[1]
+                    break
+            checkdep_inkscape.version = v
+        except (IndexError, ValueError, UnboundLocalError, OSError):
+            pass
+    return checkdep_inkscape.version
+checkdep_inkscape.version = None
+
+
+@cbook.deprecated("3.2")
+def checkdep_ps_distiller(s):
+    if not s:
+        return False
+    try:
+        _get_executable_info("gs")
+    except ExecutableNotFoundError:
+        _log.warning(
+            "Setting rcParams['ps.usedistiller'] requires ghostscript.")
+        return False
+    if s == "xpdf":
+        try:
+            _get_executable_info("pdftops")
+        except ExecutableNotFoundError:
+            _log.warning(
+                "Setting rcParams['ps.usedistiller'] to 'xpdf' requires xpdf.")
+            return False
+    return s
+
+
+def checkdep_usetex(s):
+    if not s:
+        return False
+    if not shutil.which("tex"):
+        _log.warning("usetex mode requires TeX.")
+        return False
+    try:
+        _get_executable_info("dvipng")
+    except ExecutableNotFoundError:
+        _log.warning("usetex mode requires dvipng.")
+        return False
+    try:
+        _get_executable_info("gs")
+    except ExecutableNotFoundError:
+        _log.warning("usetex mode requires ghostscript.")
+        return False
+    return True
+
+
+@cbook.deprecated("3.2", alternative="os.path.expanduser('~')")
+@_logged_cached('$HOME=%s')
+def get_home():
+    """
+    Return the user's home directory.
+
+    If the user's home directory cannot be found, return None.
+    """
+    try:
+        return str(Path.home())
+    except Exception:
+        return None
+
+
+def _create_tmp_config_or_cache_dir():
+    """
+    If the config or cache directory cannot be created, create a temporary one.
+    """
+    configdir = os.environ['MPLCONFIGDIR'] = (
+        tempfile.mkdtemp(prefix='matplotlib-'))
+    atexit.register(shutil.rmtree, configdir)
+    return configdir
+
+
+def _get_xdg_config_dir():
+    """
+    Return the XDG configuration directory, according to the `XDG
+    base directory spec
+    <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.
+    """
+    return os.environ.get('XDG_CONFIG_HOME') or str(Path.home() / ".config")
+
+
+def _get_xdg_cache_dir():
+    """
+    Return the XDG cache directory, according to the `XDG
+    base directory spec
+    <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.
+    """
+    return os.environ.get('XDG_CACHE_HOME') or str(Path.home() / ".cache")
+
+
+def _get_config_or_cache_dir(xdg_base):
+    configdir = os.environ.get('MPLCONFIGDIR')
+    if configdir:
+        configdir = Path(configdir).resolve()
+    elif sys.platform.startswith(('linux', 'freebsd')) and xdg_base:
+        configdir = Path(xdg_base, "matplotlib")
+    else:
+        configdir = Path.home() / ".matplotlib"
+    try:
+        configdir.mkdir(parents=True, exist_ok=True)
+    except OSError:
+        pass
+    else:
+        if os.access(str(configdir), os.W_OK) and configdir.is_dir():
+            return str(configdir)
+    return _create_tmp_config_or_cache_dir()
+
+
+@_logged_cached('CONFIGDIR=%s')
+def get_configdir():
+    """
+    Return the string representing the configuration directory.
+
+    The directory is chosen as follows:
+
+    1. If the MPLCONFIGDIR environment variable is supplied, choose that.
+    2a. On Linux, follow the XDG specification and look first in
+        `$XDG_CONFIG_HOME`, if defined, or `$HOME/.config`.
+    2b. On other platforms, choose `$HOME/.matplotlib`.
+    3. If the chosen directory exists and is writable, use that as the
+       configuration directory.
+    4. If possible, create a temporary directory, and use it as the
+       configuration directory.
+    5. A writable directory could not be found or created; return None.
+    """
+    return _get_config_or_cache_dir(_get_xdg_config_dir())
+
+
+@_logged_cached('CACHEDIR=%s')
+def get_cachedir():
+    """
+    Return the location of the cache directory.
+
+    The procedure used to find the directory is the same as for
+    _get_config_dir, except using `$XDG_CACHE_HOME`/`~/.cache` instead.
+    """
+    return _get_config_or_cache_dir(_get_xdg_cache_dir())
+
+
+@_logged_cached('matplotlib data path: %s')
+def get_data_path(*, _from_rc=None):
+    """Return the path to Matplotlib data."""
+    if _from_rc is not None:
+        cbook.warn_deprecated(
+            "3.2",
+            message=("Setting the datapath via matplotlibrc is "
+            "deprecated %(since)s and will be removed in %(removal)s. "
+            ""),
+            removal='3.3')
+        path = Path(_from_rc)
+        if path.is_dir():
+            defaultParams['datapath'][0] = str(path)
+            return str(path)
+        else:
+            warnings.warn(f"You passed datapath: {_from_rc!r} in your "
+                          f"matplotribrc file ({matplotlib_fname()}). "
+                          "However this path does not exist, falling back "
+                          "to standard paths.")
+
+    return _get_data_path()
+
+
+@_logged_cached('(private) matplotlib data path: %s')
+def _get_data_path():
+    if 'MATPLOTLIBDATA' in os.environ:
+        path = os.environ['MATPLOTLIBDATA']
+        if not os.path.isdir(path):
+            raise RuntimeError('Path in environment MATPLOTLIBDATA not a '
+                               'directory')
+        cbook.warn_deprecated(
+            "3.1", name="MATPLOTLIBDATA", obj_type="environment variable")
+        return path
+
+    path = Path(__file__).with_name("mpl-data")
+    if path.is_dir():
+        defaultParams['datapath'][0] = str(path)
+        return str(path)
+
+    cbook.warn_deprecated(
+        "3.2", message="Matplotlib installs where the data is not in the "
+        "mpl-data subdirectory of the package are deprecated since %(since)s "
+        "and support for them will be removed %(removal)s.")
+
+    def get_candidate_paths():
+        # setuptools' namespace_packages may hijack this init file
+        # so need to try something known to be in Matplotlib, not basemap.
+        import matplotlib.afm
+        yield Path(matplotlib.afm.__file__).with_name('mpl-data')
+        # py2exe zips pure python, so still need special check.
+        if getattr(sys, 'frozen', None):
+            yield Path(sys.executable).with_name('mpl-data')
+            # Try again assuming we need to step up one more directory.
+            yield Path(sys.executable).parent.with_name('mpl-data')
+            # Try again assuming sys.path[0] is a dir not a exe.
+            yield Path(sys.path[0]) / 'mpl-data'
+
+    for path in get_candidate_paths():
+        if path.is_dir():
+            defaultParams['datapath'][0] = str(path)
+            return str(path)
+
+    raise RuntimeError('Could not find the matplotlib data files')
+
+
+@cbook.deprecated("3.1")
+def get_py2exe_datafiles():
+    data_path = Path(get_data_path())
+    d = {}
+    for path in filter(Path.is_file, data_path.glob("**/*")):
+        (d.setdefault(str(path.parent.relative_to(data_path.parent)), [])
+         .append(str(path)))
+    return list(d.items())
+
+
+def matplotlib_fname():
+    """
+    Get the location of the config file.
+
+    The file location is determined in the following order
+
+    - ``$PWD/matplotlibrc``
+    - ``$MATPLOTLIBRC`` if it is not a directory
+    - ``$MATPLOTLIBRC/matplotlibrc``
+    - ``$MPLCONFIGDIR/matplotlibrc``
+    - On Linux,
+        - ``$XDG_CONFIG_HOME/matplotlib/matplotlibrc`` (if ``$XDG_CONFIG_HOME``
+          is defined)
+        - or ``$HOME/.config/matplotlib/matplotlibrc`` (if ``$XDG_CONFIG_HOME``
+          is not defined)
+    - On other platforms,
+      - ``$HOME/.matplotlib/matplotlibrc`` if ``$HOME`` is defined
+    - Lastly, it looks in ``$MATPLOTLIBDATA/matplotlibrc``, which should always
+      exist.
+    """
+
+    def gen_candidates():
+        yield os.path.join(os.getcwd(), 'matplotlibrc')
+        try:
+            matplotlibrc = os.environ['MATPLOTLIBRC']
+        except KeyError:
+            pass
+        else:
+            yield matplotlibrc
+            yield os.path.join(matplotlibrc, 'matplotlibrc')
+        yield os.path.join(get_configdir(), 'matplotlibrc')
+        yield os.path.join(_get_data_path(), 'matplotlibrc')
+
+    for fname in gen_candidates():
+        if os.path.exists(fname) and not os.path.isdir(fname):
+            return fname
+
+    raise RuntimeError("Could not find matplotlibrc file; your Matplotlib "
+                       "install is broken")
+
+
+# rcParams deprecated and automatically mapped to another key.
+# Values are tuples of (version, new_name, f_old2new, f_new2old).
+_deprecated_map = {}
+
+# rcParams deprecated; some can manually be mapped to another key.
+# Values are tuples of (version, new_name_or_None).
+_deprecated_ignore_map = {
+    'pgf.debug': ('3.0', None),
+}
+
+# rcParams deprecated; can use None to suppress warnings; remain actually
+# listed in the rcParams (not included in _all_deprecated).
+# Values are tuples of (version,)
+_deprecated_remain_as_none = {
+    'text.latex.unicode': ('3.0',),
+    'savefig.frameon': ('3.1',),
+    'verbose.fileo': ('3.1',),
+    'verbose.level': ('3.1',),
+    'datapath': ('3.2.1',),
+}
+
+
+_all_deprecated = {*_deprecated_map, *_deprecated_ignore_map}
+
+
+class RcParams(MutableMapping, dict):
+    """
+    A dictionary object including validation.
+
+    Validating functions are defined and associated with rc parameters in
+    :mod:`matplotlib.rcsetup`.
+
+    See Also
+    --------
+    :ref:`customizing-with-matplotlibrc-files`
+    """
+
+    validate = {key: converter
+                for key, (default, converter) in defaultParams.items()
+                if key not in _all_deprecated}
+
+    # validate values on the way in
+    def __init__(self, *args, **kwargs):
+        self.update(*args, **kwargs)
+
+    def __setitem__(self, key, val):
+        try:
+            if key in _deprecated_map:
+                version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
+                cbook.warn_deprecated(
+                    version, name=key, obj_type="rcparam", alternative=alt_key)
+                key = alt_key
+                val = alt_val(val)
+            elif key in _deprecated_remain_as_none and val is not None:
+                version, = _deprecated_remain_as_none[key]
+                cbook.warn_deprecated(
+                    version, name=key, obj_type="rcparam")
+            elif key in _deprecated_ignore_map:
+                version, alt_key = _deprecated_ignore_map[key]
+                cbook.warn_deprecated(
+                    version, name=key, obj_type="rcparam", alternative=alt_key)
+                return
+            elif key == 'backend':
+                if val is rcsetup._auto_backend_sentinel:
+                    if 'backend' in self:
+                        return
+            try:
+                cval = self.validate[key](val)
+            except ValueError as ve:
+                raise ValueError("Key %s: %s" % (key, str(ve)))
+            dict.__setitem__(self, key, cval)
+        except KeyError:
+            raise KeyError(
+                f"{key} is not a valid rc parameter (see rcParams.keys() for "
+                f"a list of valid parameters)")
+
+    def __getitem__(self, key):
+        if key in _deprecated_map:
+            version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
+            cbook.warn_deprecated(
+                version, name=key, obj_type="rcparam", alternative=alt_key)
+            return inverse_alt(dict.__getitem__(self, alt_key))
+
+        elif key in _deprecated_ignore_map:
+            version, alt_key = _deprecated_ignore_map[key]
+            cbook.warn_deprecated(
+                version, name=key, obj_type="rcparam", alternative=alt_key)
+            return dict.__getitem__(self, alt_key) if alt_key else None
+
+        elif key == "backend":
+            val = dict.__getitem__(self, key)
+            if val is rcsetup._auto_backend_sentinel:
+                from matplotlib import pyplot as plt
+                plt.switch_backend(rcsetup._auto_backend_sentinel)
+
+        return dict.__getitem__(self, key)
+
+    def __repr__(self):
+        class_name = self.__class__.__name__
+        indent = len(class_name) + 1
+        with cbook._suppress_matplotlib_deprecation_warning():
+            repr_split = pprint.pformat(dict(self), indent=1,
+                                        width=80 - indent).split('\n')
+        repr_indented = ('\n' + ' ' * indent).join(repr_split)
+        return '{}({})'.format(class_name, repr_indented)
+
+    def __str__(self):
+        return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items())))
+
+    def __iter__(self):
+        """Yield sorted list of keys."""
+        with cbook._suppress_matplotlib_deprecation_warning():
+            yield from sorted(dict.__iter__(self))
+
+    def __len__(self):
+        return dict.__len__(self)
+
+    def find_all(self, pattern):
+        """
+        Return the subset of this RcParams dictionary whose keys match,
+        using :func:`re.search`, the given ``pattern``.
+
+        .. note::
+
+            Changes to the returned dictionary are *not* propagated to
+            the parent RcParams dictionary.
+
+        """
+        pattern_re = re.compile(pattern)
+        return RcParams((key, value)
+                        for key, value in self.items()
+                        if pattern_re.search(key))
+
+    def copy(self):
+        return {k: dict.__getitem__(self, k) for k in self}
+
+
+def rc_params(fail_on_error=False):
+    """Construct a `RcParams` instance from the default Matplotlib rc file."""
+    return rc_params_from_file(matplotlib_fname(), fail_on_error)
+
+
+URL_REGEX = re.compile(r'^http://|^https://|^ftp://|^file:')
+
+
+def is_url(filename):
+    """Return True if string is an http, ftp, or file URL path."""
+    return URL_REGEX.match(filename) is not None
+
+
+@contextlib.contextmanager
+def _open_file_or_url(fname):
+    if not isinstance(fname, Path) and is_url(fname):
+        import urllib.request
+        with urllib.request.urlopen(fname) as f:
+            yield (line.decode('utf-8') for line in f)
+    else:
+        fname = os.path.expanduser(fname)
+        encoding = locale.getpreferredencoding(do_setlocale=False)
+        if encoding is None:
+            encoding = "utf-8"
+        with open(fname, encoding=encoding) as f:
+            yield f
+
+
+def _rc_params_in_file(fname, fail_on_error=False):
+    """
+    Construct a `RcParams` instance from file *fname*.
+
+    Unlike `rc_params_from_file`, the configuration class only contains the
+    parameters specified in the file (i.e. default values are not filled in).
+    """
+    _error_details_fmt = 'line #%d\n\t"%s"\n\tin file "%s"'
+
+    rc_temp = {}
+    with _open_file_or_url(fname) as fd:
+        try:
+            for line_no, line in enumerate(fd, 1):
+                strippedline = line.split('#', 1)[0].strip()
+                if not strippedline:
+                    continue
+                tup = strippedline.split(':', 1)
+                if len(tup) != 2:
+                    error_details = _error_details_fmt % (line_no, line, fname)
+                    _log.warning('Illegal %s', error_details)
+                    continue
+                key, val = tup
+                key = key.strip()
+                val = val.strip()
+                if key in rc_temp:
+                    _log.warning('Duplicate key in file %r line #%d.',
+                                 fname, line_no)
+                rc_temp[key] = (val, line, line_no)
+        except UnicodeDecodeError:
+            _log.warning('Cannot decode configuration file %s with encoding '
+                         '%s, check LANG and LC_* variables.',
+                         fname,
+                         locale.getpreferredencoding(do_setlocale=False)
+                         or 'utf-8 (default)')
+            raise
+
+    config = RcParams()
+
+    for key, (val, line, line_no) in rc_temp.items():
+        if key in defaultParams:
+            if fail_on_error:
+                config[key] = val  # try to convert to proper type or raise
+            else:
+                try:
+                    config[key] = val  # try to convert to proper type or skip
+                except Exception as msg:
+                    error_details = _error_details_fmt % (line_no, line, fname)
+                    _log.warning('Bad val %r on %s\n\t%s',
+                                 val, error_details, msg)
+        elif key in _deprecated_ignore_map:
+            version, alt_key = _deprecated_ignore_map[key]
+            cbook.warn_deprecated(
+                version, name=key, alternative=alt_key,
+                addendum="Please update your matplotlibrc.")
+        else:
+            version = 'master' if '.post' in __version__ else f'v{__version__}'
+            print(f"""
+Bad key "{key}" on line {line_no} in
+{fname}.
+You probably need to get an updated matplotlibrc file from
+https://github.com/matplotlib/matplotlib/blob/{version}/matplotlibrc.template
+or from the matplotlib source distribution""", file=sys.stderr)
+    return config
+
+
+def rc_params_from_file(fname, fail_on_error=False, use_default_template=True):
+    """
+    Construct a `RcParams` from file *fname*.
+
+    Parameters
+    ----------
+    fname : str or path-like
+        Name of file parsed for Matplotlib settings.
+    fail_on_error : bool
+        If True, raise an error when the parser fails to convert a parameter.
+    use_default_template : bool
+        If True, initialize with default parameters before updating with those
+        in the given file. If False, the configuration class only contains the
+        parameters specified in the file. (Useful for updating dicts.)
+    """
+    config_from_file = _rc_params_in_file(fname, fail_on_error)
+
+    if not use_default_template:
+        return config_from_file
+
+    iter_params = defaultParams.items()
+    with cbook._suppress_matplotlib_deprecation_warning():
+        config = RcParams([(key, default) for key, (default, _) in iter_params
+                           if key not in _all_deprecated])
+    config.update(config_from_file)
+
+    with cbook._suppress_matplotlib_deprecation_warning():
+        if config['datapath'] is None:
+            config['datapath'] = _get_data_path()
+        else:
+            config['datapath'] = get_data_path(_from_rc=config['datapath'])
+
+    if "".join(config['text.latex.preamble']):
+        _log.info("""
+*****************************************************************
+You have the following UNSUPPORTED LaTeX preamble customizations:
+%s
+Please do not ask for support with these customizations active.
+*****************************************************************
+""", '\n'.join(config['text.latex.preamble']))
+    _log.debug('loaded rc file %s', fname)
+
+    return config
+
+
+# this is the instance used by the matplotlib classes
+rcParams = rc_params()
+
+
+with cbook._suppress_matplotlib_deprecation_warning():
+    rcParamsOrig = RcParams(rcParams.copy())
+    rcParamsDefault = RcParams([(key, default) for key, (default, converter) in
+                                defaultParams.items()
+                                if key not in _all_deprecated])
+
+if rcParams['axes.formatter.use_locale']:
+    locale.setlocale(locale.LC_ALL, '')
+
+
+def rc(group, **kwargs):
+    """
+    Set the current rc params.  *group* is the grouping for the rc, e.g.,
+    for ``lines.linewidth`` the group is ``lines``, for
+    ``axes.facecolor``, the group is ``axes``, and so on.  Group may
+    also be a list or tuple of group names, e.g., (*xtick*, *ytick*).
+    *kwargs* is a dictionary attribute name/value pairs, e.g.,::
+
+      rc('lines', linewidth=2, color='r')
+
+    sets the current rc params and is equivalent to::
+
+      rcParams['lines.linewidth'] = 2
+      rcParams['lines.color'] = 'r'
+
+    The following aliases are available to save typing for interactive users:
+
+    =====   =================
+    Alias   Property
+    =====   =================
+    'lw'    'linewidth'
+    'ls'    'linestyle'
+    'c'     'color'
+    'fc'    'facecolor'
+    'ec'    'edgecolor'
+    'mew'   'markeredgewidth'
+    'aa'    'antialiased'
+    =====   =================
+
+    Thus you could abbreviate the above rc command as::
+
+          rc('lines', lw=2, c='r')
+
+    Note you can use python's kwargs dictionary facility to store
+    dictionaries of default parameters.  e.g., you can customize the
+    font rc as follows::
+
+      font = {'family' : 'monospace',
+              'weight' : 'bold',
+              'size'   : 'larger'}
+      rc('font', **font)  # pass in the font dict as kwargs
+
+    This enables you to easily switch between several configurations.  Use
+    ``matplotlib.style.use('default')`` or :func:`~matplotlib.rcdefaults` to
+    restore the default rc params after changes.
+
+    Notes
+    -----
+    Similar functionality is available by using the normal dict interface, i.e.
+    ``rcParams.update({"lines.linewidth": 2, ...})`` (but ``rcParams.update``
+    does not support abbreviations or grouping).
+    """
+
+    aliases = {
+        'lw':  'linewidth',
+        'ls':  'linestyle',
+        'c':   'color',
+        'fc':  'facecolor',
+        'ec':  'edgecolor',
+        'mew': 'markeredgewidth',
+        'aa':  'antialiased',
+        }
+
+    if isinstance(group, str):
+        group = (group,)
+    for g in group:
+        for k, v in kwargs.items():
+            name = aliases.get(k) or k
+            key = '%s.%s' % (g, name)
+            try:
+                rcParams[key] = v
+            except KeyError:
+                raise KeyError(('Unrecognized key "%s" for group "%s" and '
+                                'name "%s"') % (key, g, name))
+
+
+def rcdefaults():
+    """
+    Restore the rc params from Matplotlib's internal default style.
+
+    Style-blacklisted rc params (defined in
+    `matplotlib.style.core.STYLE_BLACKLIST`) are not updated.
+
+    See Also
+    --------
+    rc_file_defaults
+        Restore the rc params from the rc file originally loaded by Matplotlib.
+    matplotlib.style.use :
+        Use a specific style file.  Call ``style.use('default')`` to restore
+        the default style.
+    """
+    # Deprecation warnings were already handled when creating rcParamsDefault,
+    # no need to reemit them here.
+    with cbook._suppress_matplotlib_deprecation_warning():
+        from .style.core import STYLE_BLACKLIST
+        rcParams.clear()
+        rcParams.update({k: v for k, v in rcParamsDefault.items()
+                         if k not in STYLE_BLACKLIST})
+
+
+def rc_file_defaults():
+    """
+    Restore the rc params from the original rc file loaded by Matplotlib.
+
+    Style-blacklisted rc params (defined in
+    `matplotlib.style.core.STYLE_BLACKLIST`) are not updated.
+    """
+    # Deprecation warnings were already handled when creating rcParamsOrig, no
+    # need to reemit them here.
+    with cbook._suppress_matplotlib_deprecation_warning():
+        from .style.core import STYLE_BLACKLIST
+        rcParams.update({k: rcParamsOrig[k] for k in rcParamsOrig
+                         if k not in STYLE_BLACKLIST})
+
+
+def rc_file(fname, *, use_default_template=True):
+    """
+    Update rc params from file.
+
+    Style-blacklisted rc params (defined in
+    `matplotlib.style.core.STYLE_BLACKLIST`) are not updated.
+
+    Parameters
+    ----------
+    fname : str
+        Name of file parsed for matplotlib settings.
+
+    use_default_template : bool
+        If True, initialize with default parameters before updating with those
+        in the given file. If False, the current configuration persists
+        and only the parameters specified in the file are updated.
+    """
+    # Deprecation warnings were already handled in rc_params_from_file, no need
+    # to reemit them here.
+    with cbook._suppress_matplotlib_deprecation_warning():
+        from .style.core import STYLE_BLACKLIST
+        rc_from_file = rc_params_from_file(
+            fname, use_default_template=use_default_template)
+        rcParams.update({k: rc_from_file[k] for k in rc_from_file
+                         if k not in STYLE_BLACKLIST})
+
+
+class rc_context:
+    """
+    Return a context manager for managing rc settings.
+
+    This allows one to do::
+
+        with mpl.rc_context(fname='screen.rc'):
+            plt.plot(x, a)  # uses 'screen.rc'
+            with mpl.rc_context(fname='print.rc'):
+                plt.plot(x, b)  # uses 'print.rc'
+            plt.plot(x, c)  # uses 'screen.rc'
+
+    A dictionary can also be passed to the context manager::
+
+        with mpl.rc_context(rc={'text.usetex': True}, fname='screen.rc'):
+            plt.plot(x, a)
+
+    The 'rc' dictionary takes precedence over the settings loaded from
+    'fname'.  Passing a dictionary only is also valid. For example a
+    common usage is::
+
+        with mpl.rc_context(rc={'interactive': False}):
+            fig, ax = plt.subplots()
+            ax.plot(range(3), range(3))
+            fig.savefig('A.png', format='png')
+            plt.close(fig)
+    """
+    # While it may seem natural to implement rc_context using
+    # contextlib.contextmanager, that would entail always calling the finally:
+    # clause of the contextmanager (which restores the original rcs) including
+    # during garbage collection; as a result, something like `plt.xkcd();
+    # gc.collect()` would result in the style being lost (as `xkcd()` is
+    # implemented on top of rc_context, and nothing is holding onto context
+    # manager except possibly circular references.
+
+    def __init__(self, rc=None, fname=None):
+        self._orig = rcParams.copy()
+        try:
+            if fname:
+                rc_file(fname)
+            if rc:
+                rcParams.update(rc)
+        except Exception:
+            self.__fallback()
+            raise
+
+    def __fallback(self):
+        # If anything goes wrong, revert to the original rcs.
+        updated_backend = self._orig['backend']
+        dict.update(rcParams, self._orig)
+        # except for the backend.  If the context block triggered resolving
+        # the auto backend resolution keep that value around
+        if self._orig['backend'] is rcsetup._auto_backend_sentinel:
+            rcParams['backend'] = updated_backend
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_tb):
+        self.__fallback()
+
+
+@cbook._rename_parameter("3.1", "arg", "backend")
+@cbook._delete_parameter("3.1", "warn")
+def use(backend, warn=False, force=True):
+    """
+    Select the backend used for rendering and GUI integration.
+
+    Parameters
+    ----------
+    backend : str
+        The backend to switch to.  This can either be one of the standard
+        backend names, which are case-insensitive:
+
+        - interactive backends:
+          GTK3Agg, GTK3Cairo, MacOSX, nbAgg,
+          Qt4Agg, Qt4Cairo, Qt5Agg, Qt5Cairo,
+          TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo
+
+        - non-interactive backends:
+          agg, cairo, pdf, pgf, ps, svg, template
+
+        or a string of the form: ``module://my.module.name``.
+
+    warn : bool, optional, default: False
+        If True and not *force*, emit a warning if a failure-to-switch
+        `ImportError` has been suppressed.  This parameter is deprecated.
+
+    force : bool, optional, default: True
+        If True (the default), raise an `ImportError` if the backend cannot be
+        set up (either because it fails to import, or because an incompatible
+        GUI interactive framework is already running); if False, ignore the
+        failure.
+
+    See Also
+    --------
+    :ref:`backends`
+    matplotlib.get_backend
+    """
+    name = validate_backend(backend)
+    if dict.__getitem__(rcParams, 'backend') == name:
+        # Nothing to do if the requested backend is already set
+        pass
+    else:
+        # Update both rcParams and rcDefaults so restoring the defaults later
+        # with rcdefaults won't change the backend.  This is a bit of overkill
+        # as 'backend' is already in style.core.STYLE_BLACKLIST, but better to
+        # be safe.
+        rcParams['backend'] = rcParamsDefault['backend'] = name
+        try:
+            from matplotlib import pyplot as plt
+            plt.switch_backend(name)
+        except ImportError as exc:
+            if force:
+                raise
+            if warn:
+                cbook._warn_external(
+                    f"Failed to switch backend to {backend}: {exc}")
+
+
+if os.environ.get('MPLBACKEND'):
+    rcParams['backend'] = os.environ.get('MPLBACKEND')
+
+
+def get_backend():
+    """
+    Return the name of the current backend.
+
+    See Also
+    --------
+    matplotlib.use
+    """
+    return rcParams['backend']
+
+
+def interactive(b):
+    """
+    Set whether to redraw after every plotting command (e.g. `.pyplot.xlabel`).
+    """
+    rcParams['interactive'] = b
+
+
+def is_interactive():
+    """Return whether to redraw after every plotting command."""
+    return rcParams['interactive']
+
+
+@cbook.deprecated("3.1", alternative="rcParams['tk.window_focus']")
+def tk_window_focus():
+    """
+    Return true if focus maintenance under TkAgg on win32 is on.
+
+    This currently works only for python.exe and IPython.exe.
+    Both IDLE and Pythonwin.exe fail badly when tk_window_focus is on.
+    """
+    if rcParams['backend'] != 'TkAgg':
+        return False
+    return rcParams['tk.window_focus']
+
+
+default_test_modules = [
+    'matplotlib.tests',
+    'mpl_toolkits.tests',
+]
+
+
+def _init_tests():
+    # The version of FreeType to install locally for running the
+    # tests.  This must match the value in `setupext.py`
+    LOCAL_FREETYPE_VERSION = '2.6.1'
+
+    from matplotlib import ft2font
+    if (ft2font.__freetype_version__ != LOCAL_FREETYPE_VERSION or
+        ft2font.__freetype_build_type__ != 'local'):
+        _log.warning(
+            "Matplotlib is not built with the correct FreeType version to run "
+            "tests.  Set local_freetype=True in setup.cfg and rebuild. "
+            "Expect many image comparison failures below. "
+            "Expected freetype version {0}. "
+            "Found freetype version {1}. "
+            "Freetype build type is {2}local".format(
+                LOCAL_FREETYPE_VERSION,
+                ft2font.__freetype_version__,
+                "" if ft2font.__freetype_build_type__ == 'local' else "not "))
+
+    try:
+        import pytest
+    except ImportError:
+        print("matplotlib.test requires pytest to run.")
+        raise
+
+
+@cbook._delete_parameter("3.2", "switch_backend_warn")
+def test(verbosity=None, coverage=False, switch_backend_warn=True,
+         recursionlimit=0, **kwargs):
+    """Run the matplotlib test suite."""
+    _init_tests()
+    if not os.path.isdir(os.path.join(os.path.dirname(__file__), 'tests')):
+        raise ImportError("Matplotlib test data is not installed")
+
+    old_backend = get_backend()
+    old_recursionlimit = sys.getrecursionlimit()
+    try:
+        use('agg')
+        if recursionlimit:
+            sys.setrecursionlimit(recursionlimit)
+        import pytest
+
+        args = kwargs.pop('argv', [])
+        provide_default_modules = True
+        use_pyargs = True
+        for arg in args:
+            if any(arg.startswith(module_path)
+                   for module_path in default_test_modules):
+                provide_default_modules = False
+                break
+            if os.path.exists(arg):
+                provide_default_modules = False
+                use_pyargs = False
+                break
+        if use_pyargs:
+            args += ['--pyargs']
+        if provide_default_modules:
+            args += default_test_modules
+
+        if coverage:
+            args += ['--cov']
+
+        if verbosity:
+            args += ['-' + 'v' * verbosity]
+
+        retcode = pytest.main(args, **kwargs)
+    finally:
+        if old_backend.lower() != 'agg':
+            use(old_backend)
+        if recursionlimit:
+            sys.setrecursionlimit(old_recursionlimit)
+
+    return retcode
+
+
+test.__test__ = False  # pytest: this function is not a test
+
+
+def _replacer(data, value):
+    """
+    Either returns ``data[value]`` or passes ``data`` back, converts either to
+    a sequence.
+    """
+    try:
+        # if key isn't a string don't bother
+        if isinstance(value, str):
+            # try to use __getitem__
+            value = data[value]
+    except Exception:
+        # key does not exist, silently fall back to key
+        pass
+    return sanitize_sequence(value)
+
+
+def _label_from_arg(y, default_name):
+    try:
+        return y.name
+    except AttributeError:
+        if isinstance(default_name, str):
+            return default_name
+    return None
+
+
+_DATA_DOC_TITLE = """
+
+Notes
+-----
+"""
+
+_DATA_DOC_APPENDIX = """
+
+.. note::
+    In addition to the above described arguments, this function can take a
+    **data** keyword argument. If such a **data** argument is given, the
+    following arguments are replaced by **data[<arg>]**:
+
+    {replaced}
+
+    Objects passed as **data** must support item access (``data[<arg>]``) and
+    membership test (``<arg> in data``).
+"""
+
+
+def _add_data_doc(docstring, replace_names):
+    """Add documentation for a *data* field to the given docstring.
+
+    Parameters
+    ----------
+    docstring : str
+        The input docstring.
+    replace_names : list of str or None
+        The list of parameter names which arguments should be replaced by
+        ``data[name]`` (if ``data[name]`` does not throw an exception).  If
+        None, replacement is attempted for all arguments.
+
+    Returns
+    -------
+        The augmented docstring.
+    """
+    docstring = inspect.cleandoc(docstring) if docstring is not None else ""
+    repl = ("* All positional and all keyword arguments."
+            if replace_names is None else
+            ""
+            if len(replace_names) == 0 else
+            "* All arguments with the following names: {}.".format(
+                ", ".join(map(repr, sorted(replace_names)))))
+    addendum = _DATA_DOC_APPENDIX.format(replaced=repl)
+    if _DATA_DOC_TITLE not in docstring:
+        addendum = _DATA_DOC_TITLE + addendum
+    return docstring + addendum
+
+
+def _preprocess_data(func=None, *, replace_names=None, label_namer=None):
+    """
+    A decorator to add a 'data' kwarg to a function.
+
+    When applied::
+
+        @_preprocess_data()
+        def func(ax, *args, **kwargs): ...
+
+    the signature is modified to ``decorated(ax, *args, data=None, **kwargs)``
+    with the following behavior:
+
+    - if called with ``data=None``, forward the other arguments to ``func``;
+    - otherwise, *data* must be a mapping; for any argument passed in as a
+      string ``name``, replace the argument by ``data[name]`` (if this does not
+      throw an exception), then forward the arguments to ``func``.
+
+    In either case, any argument that is a `MappingView` is also converted to a
+    list.
+
+    Parameters
+    ----------
+    replace_names : list of str or None, optional, default: None
+        The list of parameter names for which lookup into *data* should be
+        attempted. If None, replacement is attempted for all arguments.
+    label_namer : str, optional, default: None
+        If set e.g. to "namer" (which must be a kwarg in the function's
+        signature -- not as ``**kwargs``), if the *namer* argument passed in is
+        a (string) key of *data* and no *label* kwarg is passed, then use the
+        (string) value of the *namer* as *label*. ::
+
+            @_preprocess_data(label_namer="foo")
+            def func(foo, label=None): ...
+
+            func("key", data={"key": value})
+            # is equivalent to
+            func.__wrapped__(value, label="key")
+    """
+
+    if func is None:  # Return the actual decorator.
+        return functools.partial(
+            _preprocess_data,
+            replace_names=replace_names, label_namer=label_namer)
+
+    sig = inspect.signature(func)
+    varargs_name = None
+    varkwargs_name = None
+    arg_names = []
+    params = list(sig.parameters.values())
+    for p in params:
+        if p.kind is Parameter.VAR_POSITIONAL:
+            varargs_name = p.name
+        elif p.kind is Parameter.VAR_KEYWORD:
+            varkwargs_name = p.name
+        else:
+            arg_names.append(p.name)
+    data_param = Parameter("data", Parameter.KEYWORD_ONLY, default=None)
+    if varkwargs_name:
+        params.insert(-1, data_param)
+    else:
+        params.append(data_param)
+    new_sig = sig.replace(parameters=params)
+    arg_names = arg_names[1:]  # remove the first "ax" / self arg
+
+    if replace_names is not None:
+        replace_names = set(replace_names)
+
+    assert (replace_names or set()) <= set(arg_names) or varkwargs_name, (
+        "Matplotlib internal error: invalid replace_names ({!r}) for {!r}"
+        .format(replace_names, func.__name__))
+    assert label_namer is None or label_namer in arg_names, (
+        "Matplotlib internal error: invalid label_namer ({!r}) for {!r}"
+            .format(label_namer, func.__name__))
+
+    @functools.wraps(func)
+    def inner(ax, *args, data=None, **kwargs):
+        if data is None:
+            return func(ax, *map(sanitize_sequence, args), **kwargs)
+
+        bound = new_sig.bind(ax, *args, **kwargs)
+        auto_label = (bound.arguments.get(label_namer)
+                      or bound.kwargs.get(label_namer))
+
+        for k, v in bound.arguments.items():
+            if k == varkwargs_name:
+                for k1, v1 in v.items():
+                    if replace_names is None or k1 in replace_names:
+                        v[k1] = _replacer(data, v1)
+            elif k == varargs_name:
+                if replace_names is None:
+                    bound.arguments[k] = tuple(_replacer(data, v1) for v1 in v)
+            else:
+                if replace_names is None or k in replace_names:
+                    bound.arguments[k] = _replacer(data, v)
+
+        new_args = bound.args
+        new_kwargs = bound.kwargs
+
+        args_and_kwargs = {**bound.arguments, **bound.kwargs}
+        if label_namer and "label" not in args_and_kwargs:
+            new_kwargs["label"] = _label_from_arg(
+                args_and_kwargs.get(label_namer), auto_label)
+
+        return func(*new_args, **new_kwargs)
+
+    inner.__doc__ = _add_data_doc(inner.__doc__, replace_names)
+    inner.__signature__ = new_sig
+    return inner
+
+
+_log.debug('matplotlib version %s', __version__)
+_log.debug('interactive is %s', is_interactive())
+_log.debug('platform is %s', sys.platform)
+_log.debug('loaded modules: %s', list(sys.modules))

+ 260 - 0
venv/lib/python3.8/site-packages/matplotlib/_animation_data.py

@@ -0,0 +1,260 @@
+# Javascript template for HTMLWriter
+JS_INCLUDE = """
+<link rel="stylesheet"
+href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/
+css/font-awesome.min.css">
+<script language="javascript">
+  function isInternetExplorer() {
+    ua = navigator.userAgent;
+    /* MSIE used to detect old browsers and Trident used to newer ones*/
+    return ua.indexOf("MSIE ") > -1 || ua.indexOf("Trident/") > -1;
+  }
+
+  /* Define the Animation class */
+  function Animation(frames, img_id, slider_id, interval, loop_select_id){
+    this.img_id = img_id;
+    this.slider_id = slider_id;
+    this.loop_select_id = loop_select_id;
+    this.interval = interval;
+    this.current_frame = 0;
+    this.direction = 0;
+    this.timer = null;
+    this.frames = new Array(frames.length);
+
+    for (var i=0; i<frames.length; i++)
+    {
+     this.frames[i] = new Image();
+     this.frames[i].src = frames[i];
+    }
+    var slider = document.getElementById(this.slider_id);
+    slider.max = this.frames.length - 1;
+    if (isInternetExplorer()) {
+        // switch from oninput to onchange because IE <= 11 does not conform
+        // with W3C specification. It ignores oninput and onchange behaves
+        // like oninput. In contrast, Mircosoft Edge behaves correctly.
+        slider.setAttribute('onchange', slider.getAttribute('oninput'));
+        slider.setAttribute('oninput', null);
+    }
+    this.set_frame(this.current_frame);
+  }
+
+  Animation.prototype.get_loop_state = function(){
+    var button_group = document[this.loop_select_id].state;
+    for (var i = 0; i < button_group.length; i++) {
+        var button = button_group[i];
+        if (button.checked) {
+            return button.value;
+        }
+    }
+    return undefined;
+  }
+
+  Animation.prototype.set_frame = function(frame){
+    this.current_frame = frame;
+    document.getElementById(this.img_id).src =
+            this.frames[this.current_frame].src;
+    document.getElementById(this.slider_id).value = this.current_frame;
+  }
+
+  Animation.prototype.next_frame = function()
+  {
+    this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
+  }
+
+  Animation.prototype.previous_frame = function()
+  {
+    this.set_frame(Math.max(0, this.current_frame - 1));
+  }
+
+  Animation.prototype.first_frame = function()
+  {
+    this.set_frame(0);
+  }
+
+  Animation.prototype.last_frame = function()
+  {
+    this.set_frame(this.frames.length - 1);
+  }
+
+  Animation.prototype.slower = function()
+  {
+    this.interval /= 0.7;
+    if(this.direction > 0){this.play_animation();}
+    else if(this.direction < 0){this.reverse_animation();}
+  }
+
+  Animation.prototype.faster = function()
+  {
+    this.interval *= 0.7;
+    if(this.direction > 0){this.play_animation();}
+    else if(this.direction < 0){this.reverse_animation();}
+  }
+
+  Animation.prototype.anim_step_forward = function()
+  {
+    this.current_frame += 1;
+    if(this.current_frame < this.frames.length){
+      this.set_frame(this.current_frame);
+    }else{
+      var loop_state = this.get_loop_state();
+      if(loop_state == "loop"){
+        this.first_frame();
+      }else if(loop_state == "reflect"){
+        this.last_frame();
+        this.reverse_animation();
+      }else{
+        this.pause_animation();
+        this.last_frame();
+      }
+    }
+  }
+
+  Animation.prototype.anim_step_reverse = function()
+  {
+    this.current_frame -= 1;
+    if(this.current_frame >= 0){
+      this.set_frame(this.current_frame);
+    }else{
+      var loop_state = this.get_loop_state();
+      if(loop_state == "loop"){
+        this.last_frame();
+      }else if(loop_state == "reflect"){
+        this.first_frame();
+        this.play_animation();
+      }else{
+        this.pause_animation();
+        this.first_frame();
+      }
+    }
+  }
+
+  Animation.prototype.pause_animation = function()
+  {
+    this.direction = 0;
+    if (this.timer){
+      clearInterval(this.timer);
+      this.timer = null;
+    }
+  }
+
+  Animation.prototype.play_animation = function()
+  {
+    this.pause_animation();
+    this.direction = 1;
+    var t = this;
+    if (!this.timer) this.timer = setInterval(function() {
+        t.anim_step_forward();
+    }, this.interval);
+  }
+
+  Animation.prototype.reverse_animation = function()
+  {
+    this.pause_animation();
+    this.direction = -1;
+    var t = this;
+    if (!this.timer) this.timer = setInterval(function() {
+        t.anim_step_reverse();
+    }, this.interval);
+  }
+</script>
+"""
+
+
+# Style definitions for the HTML template
+STYLE_INCLUDE = """
+<style>
+.animation {
+    display: inline-block;
+    text-align: center;
+}
+input[type=range].anim-slider {
+    width: 374px;
+    margin-left: auto;
+    margin-right: auto;
+}
+.anim-buttons {
+    margin: 8px 0px;
+}
+.anim-buttons button {
+    padding: 0;
+    width: 36px;
+}
+.anim-state label {
+    margin-right: 8px;
+}
+.anim-state input {
+    margin: 0;
+    vertical-align: middle;
+}
+</style>
+"""
+
+
+# HTML template for HTMLWriter
+DISPLAY_TEMPLATE = """
+<div class="animation">
+  <img id="_anim_img{id}">
+  <div class="anim-controls">
+    <input id="_anim_slider{id}" type="range" class="anim-slider"
+           name="points" min="0" max="1" step="1" value="0"
+           oninput="anim{id}.set_frame(parseInt(this.value));"></input>
+    <div class="anim-buttons">
+      <button onclick="anim{id}.slower()"><i class="fa fa-minus"></i></button>
+      <button onclick="anim{id}.first_frame()"><i class="fa fa-fast-backward">
+          </i></button>
+      <button onclick="anim{id}.previous_frame()">
+          <i class="fa fa-step-backward"></i></button>
+      <button onclick="anim{id}.reverse_animation()">
+          <i class="fa fa-play fa-flip-horizontal"></i></button>
+      <button onclick="anim{id}.pause_animation()"><i class="fa fa-pause">
+          </i></button>
+      <button onclick="anim{id}.play_animation()"><i class="fa fa-play"></i>
+          </button>
+      <button onclick="anim{id}.next_frame()"><i class="fa fa-step-forward">
+          </i></button>
+      <button onclick="anim{id}.last_frame()"><i class="fa fa-fast-forward">
+          </i></button>
+      <button onclick="anim{id}.faster()"><i class="fa fa-plus"></i></button>
+    </div>
+    <form action="#n" name="_anim_loop_select{id}" class="anim-state">
+      <input type="radio" name="state" value="once" id="_anim_radio1_{id}"
+             {once_checked}>
+      <label for="_anim_radio1_{id}">Once</label>
+      <input type="radio" name="state" value="loop" id="_anim_radio2_{id}"
+             {loop_checked}>
+      <label for="_anim_radio2_{id}">Loop</label>
+      <input type="radio" name="state" value="reflect" id="_anim_radio3_{id}"
+             {reflect_checked}>
+      <label for="_anim_radio3_{id}">Reflect</label>
+    </form>
+  </div>
+</div>
+
+
+<script language="javascript">
+  /* Instantiate the Animation class. */
+  /* The IDs given should match those used in the template above. */
+  (function() {{
+    var img_id = "_anim_img{id}";
+    var slider_id = "_anim_slider{id}";
+    var loop_select_id = "_anim_loop_select{id}";
+    var frames = new Array({Nframes});
+    {fill_frames}
+
+    /* set a timeout to make sure all the above elements are created before
+       the object is initialized. */
+    setTimeout(function() {{
+        anim{id} = new Animation(frames, img_id, slider_id, {interval},
+                                 loop_select_id);
+    }}, 0);
+  }})()
+</script>
+"""
+
+
+INCLUDED_FRAMES = """
+  for (var i=0; i<{Nframes}; i++){{
+    frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
+                ".{frame_format}";
+  }}
+"""

+ 1426 - 0
venv/lib/python3.8/site-packages/matplotlib/_cm.py

@@ -0,0 +1,1426 @@
+"""
+Nothing here but dictionaries for generating LinearSegmentedColormaps,
+and a dictionary of these dictionaries.
+
+Documentation for each is in pyplot.colormaps().  Please update this
+with the purpose and type of your colormap if you add data for one here.
+"""
+
+from functools import partial
+
+import numpy as np
+
+_binary_data = {
+    'red':    ((0., 1., 1.), (1., 0., 0.)),
+    'green':  ((0., 1., 1.), (1., 0., 0.)),
+    'blue':   ((0., 1., 1.), (1., 0., 0.))
+    }
+
+_autumn_data = {'red':   ((0., 1.0, 1.0), (1.0, 1.0, 1.0)),
+                'green': ((0., 0., 0.), (1.0, 1.0, 1.0)),
+                'blue':  ((0., 0., 0.), (1.0, 0., 0.))}
+
+_bone_data = {'red':   ((0., 0., 0.),
+                        (0.746032, 0.652778, 0.652778),
+                        (1.0, 1.0, 1.0)),
+              'green': ((0., 0., 0.),
+                        (0.365079, 0.319444, 0.319444),
+                        (0.746032, 0.777778, 0.777778),
+                        (1.0, 1.0, 1.0)),
+              'blue':  ((0., 0., 0.),
+                        (0.365079, 0.444444, 0.444444),
+                        (1.0, 1.0, 1.0))}
+
+_cool_data = {'red':   ((0., 0., 0.), (1.0, 1.0, 1.0)),
+              'green': ((0., 1., 1.), (1.0, 0.,  0.)),
+              'blue':  ((0., 1., 1.), (1.0, 1.,  1.))}
+
+_copper_data = {'red':   ((0., 0., 0.),
+                          (0.809524, 1.000000, 1.000000),
+                          (1.0, 1.0, 1.0)),
+                'green': ((0., 0., 0.),
+                          (1.0, 0.7812, 0.7812)),
+                'blue':  ((0., 0., 0.),
+                          (1.0, 0.4975, 0.4975))}
+
+def _flag_red(x): return 0.75 * np.sin((x * 31.5 + 0.25) * np.pi) + 0.5
+def _flag_green(x): return np.sin(x * 31.5 * np.pi)
+def _flag_blue(x): return 0.75 * np.sin((x * 31.5 - 0.25) * np.pi) + 0.5
+_flag_data = {'red': _flag_red, 'green': _flag_green, 'blue': _flag_blue}
+
+def _prism_red(x): return 0.75 * np.sin((x * 20.9 + 0.25) * np.pi) + 0.67
+def _prism_green(x): return 0.75 * np.sin((x * 20.9 - 0.25) * np.pi) + 0.33
+def _prism_blue(x): return -1.1 * np.sin((x * 20.9) * np.pi)
+_prism_data = {'red': _prism_red, 'green': _prism_green, 'blue': _prism_blue}
+
+def _ch_helper(gamma, s, r, h, p0, p1, x):
+    """Helper function for generating picklable cubehelix color maps."""
+    # Apply gamma factor to emphasise low or high intensity values
+    xg = x ** gamma
+    # Calculate amplitude and angle of deviation from the black to white
+    # diagonal in the plane of constant perceived intensity.
+    a = h * xg * (1 - xg) / 2
+    phi = 2 * np.pi * (s / 3 + r * x)
+    return xg + a * (p0 * np.cos(phi) + p1 * np.sin(phi))
+
+def cubehelix(gamma=1.0, s=0.5, r=-1.5, h=1.0):
+    """
+    Return custom data dictionary of (r, g, b) conversion functions, which can
+    be used with :func:`register_cmap`, for the cubehelix color scheme.
+
+    Unlike most other color schemes cubehelix was designed by D.A. Green to
+    be monotonically increasing in terms of perceived brightness.
+    Also, when printed on a black and white postscript printer, the scheme
+    results in a greyscale with monotonically increasing brightness.
+    This color scheme is named cubehelix because the (r, g, b) values produced
+    can be visualised as a squashed helix around the diagonal in the
+    (r, g, b) color cube.
+
+    For a unit color cube (i.e. 3-D coordinates for (r, g, b) each in the
+    range 0 to 1) the color scheme starts at (r, g, b) = (0, 0, 0), i.e. black,
+    and finishes at (r, g, b) = (1, 1, 1), i.e. white. For some fraction *x*,
+    between 0 and 1, the color is the corresponding grey value at that
+    fraction along the black to white diagonal (x, x, x) plus a color
+    element. This color element is calculated in a plane of constant
+    perceived intensity and controlled by the following parameters.
+
+    Optional keyword arguments:
+
+      =========   =======================================================
+      Keyword     Description
+      =========   =======================================================
+      gamma       gamma factor to emphasise either low intensity values
+                  (gamma < 1), or high intensity values (gamma > 1);
+                  defaults to 1.0.
+      s           the start color; defaults to 0.5 (i.e. purple).
+      r           the number of r, g, b rotations in color that are made
+                  from the start to the end of the color scheme; defaults
+                  to -1.5 (i.e. -> B -> G -> R -> B).
+      h           the hue parameter which controls how saturated the
+                  colors are. If this parameter is zero then the color
+                  scheme is purely a greyscale; defaults to 1.0.
+      =========   =======================================================
+    """
+    return {'red': partial(_ch_helper, gamma, s, r, h, -0.14861, 1.78277),
+            'green': partial(_ch_helper, gamma, s, r, h, -0.29227, -0.90649),
+            'blue': partial(_ch_helper, gamma, s, r, h, 1.97294, 0.0)}
+
+_cubehelix_data = cubehelix()
+
+_bwr_data = ((0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 0.0))
+_brg_data = ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0))
+
+# Gnuplot palette functions
+def _g0(x): return 0
+def _g1(x): return 0.5
+def _g2(x): return 1
+def _g3(x): return x
+def _g4(x): return x ** 2
+def _g5(x): return x ** 3
+def _g6(x): return x ** 4
+def _g7(x): return np.sqrt(x)
+def _g8(x): return np.sqrt(np.sqrt(x))
+def _g9(x): return np.sin(x * np.pi / 2)
+def _g10(x): return np.cos(x * np.pi / 2)
+def _g11(x): return np.abs(x - 0.5)
+def _g12(x): return (2 * x - 1) ** 2
+def _g13(x): return np.sin(x * np.pi)
+def _g14(x): return np.abs(np.cos(x * np.pi))
+def _g15(x): return np.sin(x * 2 * np.pi)
+def _g16(x): return np.cos(x * 2 * np.pi)
+def _g17(x): return np.abs(np.sin(x * 2 * np.pi))
+def _g18(x): return np.abs(np.cos(x * 2 * np.pi))
+def _g19(x): return np.abs(np.sin(x * 4 * np.pi))
+def _g20(x): return np.abs(np.cos(x * 4 * np.pi))
+def _g21(x): return 3 * x
+def _g22(x): return 3 * x - 1
+def _g23(x): return 3 * x - 2
+def _g24(x): return np.abs(3 * x - 1)
+def _g25(x): return np.abs(3 * x - 2)
+def _g26(x): return (3 * x - 1) / 2
+def _g27(x): return (3 * x - 2) / 2
+def _g28(x): return np.abs((3 * x - 1) / 2)
+def _g29(x): return np.abs((3 * x - 2) / 2)
+def _g30(x): return x / 0.32 - 0.78125
+def _g31(x): return 2 * x - 0.84
+def _g32(x):
+    ret = np.zeros(len(x))
+    m = (x < 0.25)
+    ret[m] = 4 * x[m]
+    m = (x >= 0.25) & (x < 0.92)
+    ret[m] = -2 * x[m] + 1.84
+    m = (x >= 0.92)
+    ret[m] = x[m] / 0.08 - 11.5
+    return ret
+def _g33(x): return np.abs(2 * x - 0.5)
+def _g34(x): return 2 * x
+def _g35(x): return 2 * x - 0.5
+def _g36(x): return 2 * x - 1
+
+gfunc = {i: globals()["_g{}".format(i)] for i in range(37)}
+
+_gnuplot_data = {
+        'red': gfunc[7],
+        'green': gfunc[5],
+        'blue': gfunc[15],
+}
+
+_gnuplot2_data = {
+        'red': gfunc[30],
+        'green': gfunc[31],
+        'blue': gfunc[32],
+}
+
+_ocean_data = {
+        'red': gfunc[23],
+        'green': gfunc[28],
+        'blue': gfunc[3],
+}
+
+_afmhot_data = {
+        'red': gfunc[34],
+        'green': gfunc[35],
+        'blue': gfunc[36],
+}
+
+_rainbow_data = {
+        'red': gfunc[33],
+        'green': gfunc[13],
+        'blue': gfunc[10],
+}
+
+_seismic_data = (
+        (0.0, 0.0, 0.3), (0.0, 0.0, 1.0),
+        (1.0, 1.0, 1.0), (1.0, 0.0, 0.0),
+        (0.5, 0.0, 0.0))
+
+_terrain_data = (
+        (0.00, (0.2, 0.2, 0.6)),
+        (0.15, (0.0, 0.6, 1.0)),
+        (0.25, (0.0, 0.8, 0.4)),
+        (0.50, (1.0, 1.0, 0.6)),
+        (0.75, (0.5, 0.36, 0.33)),
+        (1.00, (1.0, 1.0, 1.0)))
+
+_gray_data = {'red':   ((0., 0, 0), (1., 1, 1)),
+              'green': ((0., 0, 0), (1., 1, 1)),
+              'blue':  ((0., 0, 0), (1., 1, 1))}
+
+_hot_data = {'red':   ((0., 0.0416, 0.0416),
+                       (0.365079, 1.000000, 1.000000),
+                       (1.0, 1.0, 1.0)),
+             'green': ((0., 0., 0.),
+                       (0.365079, 0.000000, 0.000000),
+                       (0.746032, 1.000000, 1.000000),
+                       (1.0, 1.0, 1.0)),
+             'blue':  ((0., 0., 0.),
+                       (0.746032, 0.000000, 0.000000),
+                       (1.0, 1.0, 1.0))}
+
+_hsv_data = {'red':   ((0., 1., 1.),
+                       (0.158730, 1.000000, 1.000000),
+                       (0.174603, 0.968750, 0.968750),
+                       (0.333333, 0.031250, 0.031250),
+                       (0.349206, 0.000000, 0.000000),
+                       (0.666667, 0.000000, 0.000000),
+                       (0.682540, 0.031250, 0.031250),
+                       (0.841270, 0.968750, 0.968750),
+                       (0.857143, 1.000000, 1.000000),
+                       (1.0, 1.0, 1.0)),
+             'green': ((0., 0., 0.),
+                       (0.158730, 0.937500, 0.937500),
+                       (0.174603, 1.000000, 1.000000),
+                       (0.507937, 1.000000, 1.000000),
+                       (0.666667, 0.062500, 0.062500),
+                       (0.682540, 0.000000, 0.000000),
+                       (1.0, 0., 0.)),
+             'blue':  ((0., 0., 0.),
+                       (0.333333, 0.000000, 0.000000),
+                       (0.349206, 0.062500, 0.062500),
+                       (0.507937, 1.000000, 1.000000),
+                       (0.841270, 1.000000, 1.000000),
+                       (0.857143, 0.937500, 0.937500),
+                       (1.0, 0.09375, 0.09375))}
+
+_jet_data = {'red':   ((0., 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89, 1, 1),
+                         (1, 0.5, 0.5)),
+             'green': ((0., 0, 0), (0.125, 0, 0), (0.375, 1, 1), (0.64, 1, 1),
+                         (0.91, 0, 0), (1, 0, 0)),
+             'blue':  ((0., 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1),
+                         (0.65, 0, 0), (1, 0, 0))}
+
+_pink_data = {'red':   ((0., 0.1178, 0.1178), (0.015873, 0.195857, 0.195857),
+                        (0.031746, 0.250661, 0.250661),
+                        (0.047619, 0.295468, 0.295468),
+                        (0.063492, 0.334324, 0.334324),
+                        (0.079365, 0.369112, 0.369112),
+                        (0.095238, 0.400892, 0.400892),
+                        (0.111111, 0.430331, 0.430331),
+                        (0.126984, 0.457882, 0.457882),
+                        (0.142857, 0.483867, 0.483867),
+                        (0.158730, 0.508525, 0.508525),
+                        (0.174603, 0.532042, 0.532042),
+                        (0.190476, 0.554563, 0.554563),
+                        (0.206349, 0.576204, 0.576204),
+                        (0.222222, 0.597061, 0.597061),
+                        (0.238095, 0.617213, 0.617213),
+                        (0.253968, 0.636729, 0.636729),
+                        (0.269841, 0.655663, 0.655663),
+                        (0.285714, 0.674066, 0.674066),
+                        (0.301587, 0.691980, 0.691980),
+                        (0.317460, 0.709441, 0.709441),
+                        (0.333333, 0.726483, 0.726483),
+                        (0.349206, 0.743134, 0.743134),
+                        (0.365079, 0.759421, 0.759421),
+                        (0.380952, 0.766356, 0.766356),
+                        (0.396825, 0.773229, 0.773229),
+                        (0.412698, 0.780042, 0.780042),
+                        (0.428571, 0.786796, 0.786796),
+                        (0.444444, 0.793492, 0.793492),
+                        (0.460317, 0.800132, 0.800132),
+                        (0.476190, 0.806718, 0.806718),
+                        (0.492063, 0.813250, 0.813250),
+                        (0.507937, 0.819730, 0.819730),
+                        (0.523810, 0.826160, 0.826160),
+                        (0.539683, 0.832539, 0.832539),
+                        (0.555556, 0.838870, 0.838870),
+                        (0.571429, 0.845154, 0.845154),
+                        (0.587302, 0.851392, 0.851392),
+                        (0.603175, 0.857584, 0.857584),
+                        (0.619048, 0.863731, 0.863731),
+                        (0.634921, 0.869835, 0.869835),
+                        (0.650794, 0.875897, 0.875897),
+                        (0.666667, 0.881917, 0.881917),
+                        (0.682540, 0.887896, 0.887896),
+                        (0.698413, 0.893835, 0.893835),
+                        (0.714286, 0.899735, 0.899735),
+                        (0.730159, 0.905597, 0.905597),
+                        (0.746032, 0.911421, 0.911421),
+                        (0.761905, 0.917208, 0.917208),
+                        (0.777778, 0.922958, 0.922958),
+                        (0.793651, 0.928673, 0.928673),
+                        (0.809524, 0.934353, 0.934353),
+                        (0.825397, 0.939999, 0.939999),
+                        (0.841270, 0.945611, 0.945611),
+                        (0.857143, 0.951190, 0.951190),
+                        (0.873016, 0.956736, 0.956736),
+                        (0.888889, 0.962250, 0.962250),
+                        (0.904762, 0.967733, 0.967733),
+                        (0.920635, 0.973185, 0.973185),
+                        (0.936508, 0.978607, 0.978607),
+                        (0.952381, 0.983999, 0.983999),
+                        (0.968254, 0.989361, 0.989361),
+                        (0.984127, 0.994695, 0.994695), (1.0, 1.0, 1.0)),
+              'green': ((0., 0., 0.), (0.015873, 0.102869, 0.102869),
+                        (0.031746, 0.145479, 0.145479),
+                        (0.047619, 0.178174, 0.178174),
+                        (0.063492, 0.205738, 0.205738),
+                        (0.079365, 0.230022, 0.230022),
+                        (0.095238, 0.251976, 0.251976),
+                        (0.111111, 0.272166, 0.272166),
+                        (0.126984, 0.290957, 0.290957),
+                        (0.142857, 0.308607, 0.308607),
+                        (0.158730, 0.325300, 0.325300),
+                        (0.174603, 0.341178, 0.341178),
+                        (0.190476, 0.356348, 0.356348),
+                        (0.206349, 0.370899, 0.370899),
+                        (0.222222, 0.384900, 0.384900),
+                        (0.238095, 0.398410, 0.398410),
+                        (0.253968, 0.411476, 0.411476),
+                        (0.269841, 0.424139, 0.424139),
+                        (0.285714, 0.436436, 0.436436),
+                        (0.301587, 0.448395, 0.448395),
+                        (0.317460, 0.460044, 0.460044),
+                        (0.333333, 0.471405, 0.471405),
+                        (0.349206, 0.482498, 0.482498),
+                        (0.365079, 0.493342, 0.493342),
+                        (0.380952, 0.517549, 0.517549),
+                        (0.396825, 0.540674, 0.540674),
+                        (0.412698, 0.562849, 0.562849),
+                        (0.428571, 0.584183, 0.584183),
+                        (0.444444, 0.604765, 0.604765),
+                        (0.460317, 0.624669, 0.624669),
+                        (0.476190, 0.643958, 0.643958),
+                        (0.492063, 0.662687, 0.662687),
+                        (0.507937, 0.680900, 0.680900),
+                        (0.523810, 0.698638, 0.698638),
+                        (0.539683, 0.715937, 0.715937),
+                        (0.555556, 0.732828, 0.732828),
+                        (0.571429, 0.749338, 0.749338),
+                        (0.587302, 0.765493, 0.765493),
+                        (0.603175, 0.781313, 0.781313),
+                        (0.619048, 0.796819, 0.796819),
+                        (0.634921, 0.812029, 0.812029),
+                        (0.650794, 0.826960, 0.826960),
+                        (0.666667, 0.841625, 0.841625),
+                        (0.682540, 0.856040, 0.856040),
+                        (0.698413, 0.870216, 0.870216),
+                        (0.714286, 0.884164, 0.884164),
+                        (0.730159, 0.897896, 0.897896),
+                        (0.746032, 0.911421, 0.911421),
+                        (0.761905, 0.917208, 0.917208),
+                        (0.777778, 0.922958, 0.922958),
+                        (0.793651, 0.928673, 0.928673),
+                        (0.809524, 0.934353, 0.934353),
+                        (0.825397, 0.939999, 0.939999),
+                        (0.841270, 0.945611, 0.945611),
+                        (0.857143, 0.951190, 0.951190),
+                        (0.873016, 0.956736, 0.956736),
+                        (0.888889, 0.962250, 0.962250),
+                        (0.904762, 0.967733, 0.967733),
+                        (0.920635, 0.973185, 0.973185),
+                        (0.936508, 0.978607, 0.978607),
+                        (0.952381, 0.983999, 0.983999),
+                        (0.968254, 0.989361, 0.989361),
+                        (0.984127, 0.994695, 0.994695), (1.0, 1.0, 1.0)),
+              'blue':  ((0., 0., 0.), (0.015873, 0.102869, 0.102869),
+                        (0.031746, 0.145479, 0.145479),
+                        (0.047619, 0.178174, 0.178174),
+                        (0.063492, 0.205738, 0.205738),
+                        (0.079365, 0.230022, 0.230022),
+                        (0.095238, 0.251976, 0.251976),
+                        (0.111111, 0.272166, 0.272166),
+                        (0.126984, 0.290957, 0.290957),
+                        (0.142857, 0.308607, 0.308607),
+                        (0.158730, 0.325300, 0.325300),
+                        (0.174603, 0.341178, 0.341178),
+                        (0.190476, 0.356348, 0.356348),
+                        (0.206349, 0.370899, 0.370899),
+                        (0.222222, 0.384900, 0.384900),
+                        (0.238095, 0.398410, 0.398410),
+                        (0.253968, 0.411476, 0.411476),
+                        (0.269841, 0.424139, 0.424139),
+                        (0.285714, 0.436436, 0.436436),
+                        (0.301587, 0.448395, 0.448395),
+                        (0.317460, 0.460044, 0.460044),
+                        (0.333333, 0.471405, 0.471405),
+                        (0.349206, 0.482498, 0.482498),
+                        (0.365079, 0.493342, 0.493342),
+                        (0.380952, 0.503953, 0.503953),
+                        (0.396825, 0.514344, 0.514344),
+                        (0.412698, 0.524531, 0.524531),
+                        (0.428571, 0.534522, 0.534522),
+                        (0.444444, 0.544331, 0.544331),
+                        (0.460317, 0.553966, 0.553966),
+                        (0.476190, 0.563436, 0.563436),
+                        (0.492063, 0.572750, 0.572750),
+                        (0.507937, 0.581914, 0.581914),
+                        (0.523810, 0.590937, 0.590937),
+                        (0.539683, 0.599824, 0.599824),
+                        (0.555556, 0.608581, 0.608581),
+                        (0.571429, 0.617213, 0.617213),
+                        (0.587302, 0.625727, 0.625727),
+                        (0.603175, 0.634126, 0.634126),
+                        (0.619048, 0.642416, 0.642416),
+                        (0.634921, 0.650600, 0.650600),
+                        (0.650794, 0.658682, 0.658682),
+                        (0.666667, 0.666667, 0.666667),
+                        (0.682540, 0.674556, 0.674556),
+                        (0.698413, 0.682355, 0.682355),
+                        (0.714286, 0.690066, 0.690066),
+                        (0.730159, 0.697691, 0.697691),
+                        (0.746032, 0.705234, 0.705234),
+                        (0.761905, 0.727166, 0.727166),
+                        (0.777778, 0.748455, 0.748455),
+                        (0.793651, 0.769156, 0.769156),
+                        (0.809524, 0.789314, 0.789314),
+                        (0.825397, 0.808969, 0.808969),
+                        (0.841270, 0.828159, 0.828159),
+                        (0.857143, 0.846913, 0.846913),
+                        (0.873016, 0.865261, 0.865261),
+                        (0.888889, 0.883229, 0.883229),
+                        (0.904762, 0.900837, 0.900837),
+                        (0.920635, 0.918109, 0.918109),
+                        (0.936508, 0.935061, 0.935061),
+                        (0.952381, 0.951711, 0.951711),
+                        (0.968254, 0.968075, 0.968075),
+                        (0.984127, 0.984167, 0.984167), (1.0, 1.0, 1.0))}
+
+_spring_data = {'red':   ((0., 1., 1.), (1.0, 1.0, 1.0)),
+                'green': ((0., 0., 0.), (1.0, 1.0, 1.0)),
+                'blue':  ((0., 1., 1.), (1.0, 0.0, 0.0))}
+
+
+_summer_data = {'red':   ((0., 0., 0.), (1.0, 1.0, 1.0)),
+                'green': ((0., 0.5, 0.5), (1.0, 1.0, 1.0)),
+                'blue':  ((0., 0.4, 0.4), (1.0, 0.4, 0.4))}
+
+
+_winter_data = {'red':   ((0., 0., 0.), (1.0, 0.0, 0.0)),
+                'green': ((0., 0., 0.), (1.0, 1.0, 1.0)),
+                'blue':  ((0., 1., 1.), (1.0, 0.5, 0.5))}
+
+_nipy_spectral_data = {
+      'red': [(0.0, 0.0, 0.0), (0.05, 0.4667, 0.4667),
+              (0.10, 0.5333, 0.5333), (0.15, 0.0, 0.0),
+              (0.20, 0.0, 0.0), (0.25, 0.0, 0.0),
+              (0.30, 0.0, 0.0), (0.35, 0.0, 0.0),
+              (0.40, 0.0, 0.0), (0.45, 0.0, 0.0),
+              (0.50, 0.0, 0.0), (0.55, 0.0, 0.0),
+              (0.60, 0.0, 0.0), (0.65, 0.7333, 0.7333),
+              (0.70, 0.9333, 0.9333), (0.75, 1.0, 1.0),
+              (0.80, 1.0, 1.0), (0.85, 1.0, 1.0),
+              (0.90, 0.8667, 0.8667), (0.95, 0.80, 0.80),
+              (1.0, 0.80, 0.80)],
+    'green': [(0.0, 0.0, 0.0), (0.05, 0.0, 0.0),
+              (0.10, 0.0, 0.0), (0.15, 0.0, 0.0),
+              (0.20, 0.0, 0.0), (0.25, 0.4667, 0.4667),
+              (0.30, 0.6000, 0.6000), (0.35, 0.6667, 0.6667),
+              (0.40, 0.6667, 0.6667), (0.45, 0.6000, 0.6000),
+              (0.50, 0.7333, 0.7333), (0.55, 0.8667, 0.8667),
+              (0.60, 1.0, 1.0), (0.65, 1.0, 1.0),
+              (0.70, 0.9333, 0.9333), (0.75, 0.8000, 0.8000),
+              (0.80, 0.6000, 0.6000), (0.85, 0.0, 0.0),
+              (0.90, 0.0, 0.0), (0.95, 0.0, 0.0),
+              (1.0, 0.80, 0.80)],
+     'blue': [(0.0, 0.0, 0.0), (0.05, 0.5333, 0.5333),
+              (0.10, 0.6000, 0.6000), (0.15, 0.6667, 0.6667),
+              (0.20, 0.8667, 0.8667), (0.25, 0.8667, 0.8667),
+              (0.30, 0.8667, 0.8667), (0.35, 0.6667, 0.6667),
+              (0.40, 0.5333, 0.5333), (0.45, 0.0, 0.0),
+              (0.5, 0.0, 0.0), (0.55, 0.0, 0.0),
+              (0.60, 0.0, 0.0), (0.65, 0.0, 0.0),
+              (0.70, 0.0, 0.0), (0.75, 0.0, 0.0),
+              (0.80, 0.0, 0.0), (0.85, 0.0, 0.0),
+              (0.90, 0.0, 0.0), (0.95, 0.0, 0.0),
+              (1.0, 0.80, 0.80)],
+}
+
+
+# 34 colormaps based on color specifications and designs
+# developed by Cynthia Brewer (http://colorbrewer.org).
+# The ColorBrewer palettes have been included under the terms
+# of an Apache-stype license (for details, see the file
+# LICENSE_COLORBREWER in the license directory of the matplotlib
+# source distribution).
+
+# RGB values taken from Brewer's Excel sheet, divided by 255
+
+_Blues_data = (
+    (0.96862745098039216,  0.98431372549019602,  1.0                ),
+    (0.87058823529411766,  0.92156862745098034,  0.96862745098039216),
+    (0.77647058823529413,  0.85882352941176465,  0.93725490196078431),
+    (0.61960784313725492,  0.792156862745098  ,  0.88235294117647056),
+    (0.41960784313725491,  0.68235294117647061,  0.83921568627450982),
+    (0.25882352941176473,  0.5725490196078431 ,  0.77647058823529413),
+    (0.12941176470588237,  0.44313725490196076,  0.70980392156862748),
+    (0.03137254901960784,  0.31764705882352939,  0.61176470588235299),
+    (0.03137254901960784,  0.18823529411764706,  0.41960784313725491)
+    )
+
+_BrBG_data = (
+    (0.32941176470588235,  0.18823529411764706,  0.0196078431372549 ),
+    (0.5490196078431373 ,  0.31764705882352939,  0.0392156862745098 ),
+    (0.74901960784313726,  0.50588235294117645,  0.17647058823529413),
+    (0.87450980392156863,  0.76078431372549016,  0.49019607843137253),
+    (0.96470588235294119,  0.90980392156862744,  0.76470588235294112),
+    (0.96078431372549022,  0.96078431372549022,  0.96078431372549022),
+    (0.7803921568627451 ,  0.91764705882352937,  0.89803921568627454),
+    (0.50196078431372548,  0.80392156862745101,  0.75686274509803919),
+    (0.20784313725490197,  0.59215686274509804,  0.5607843137254902 ),
+    (0.00392156862745098,  0.4                ,  0.36862745098039218),
+    (0.0                ,  0.23529411764705882,  0.18823529411764706)
+    )
+
+_BuGn_data = (
+    (0.96862745098039216,  0.9882352941176471 ,  0.99215686274509807),
+    (0.89803921568627454,  0.96078431372549022,  0.97647058823529409),
+    (0.8                ,  0.92549019607843142,  0.90196078431372551),
+    (0.6                ,  0.84705882352941175,  0.78823529411764703),
+    (0.4                ,  0.76078431372549016,  0.64313725490196083),
+    (0.25490196078431371,  0.68235294117647061,  0.46274509803921571),
+    (0.13725490196078433,  0.54509803921568623,  0.27058823529411763),
+    (0.0                ,  0.42745098039215684,  0.17254901960784313),
+    (0.0                ,  0.26666666666666666,  0.10588235294117647)
+    )
+
+_BuPu_data = (
+    (0.96862745098039216,  0.9882352941176471 ,  0.99215686274509807),
+    (0.8784313725490196 ,  0.92549019607843142,  0.95686274509803926),
+    (0.74901960784313726,  0.82745098039215681,  0.90196078431372551),
+    (0.61960784313725492,  0.73725490196078436,  0.85490196078431369),
+    (0.5490196078431373 ,  0.58823529411764708,  0.77647058823529413),
+    (0.5490196078431373 ,  0.41960784313725491,  0.69411764705882351),
+    (0.53333333333333333,  0.25490196078431371,  0.61568627450980395),
+    (0.50588235294117645,  0.05882352941176471,  0.48627450980392156),
+    (0.30196078431372547,  0.0                ,  0.29411764705882354)
+    )
+
+_GnBu_data = (
+    (0.96862745098039216,  0.9882352941176471 ,  0.94117647058823528),
+    (0.8784313725490196 ,  0.95294117647058818,  0.85882352941176465),
+    (0.8                ,  0.92156862745098034,  0.77254901960784317),
+    (0.6588235294117647 ,  0.8666666666666667 ,  0.70980392156862748),
+    (0.4823529411764706 ,  0.8                ,  0.7686274509803922 ),
+    (0.30588235294117649,  0.70196078431372544,  0.82745098039215681),
+    (0.16862745098039217,  0.5490196078431373 ,  0.74509803921568629),
+    (0.03137254901960784,  0.40784313725490196,  0.67450980392156867),
+    (0.03137254901960784,  0.25098039215686274,  0.50588235294117645)
+    )
+
+_Greens_data = (
+    (0.96862745098039216,  0.9882352941176471 ,  0.96078431372549022),
+    (0.89803921568627454,  0.96078431372549022,  0.8784313725490196 ),
+    (0.7803921568627451 ,  0.9137254901960784 ,  0.75294117647058822),
+    (0.63137254901960782,  0.85098039215686272,  0.60784313725490191),
+    (0.45490196078431372,  0.7686274509803922 ,  0.46274509803921571),
+    (0.25490196078431371,  0.6705882352941176 ,  0.36470588235294116),
+    (0.13725490196078433,  0.54509803921568623,  0.27058823529411763),
+    (0.0                ,  0.42745098039215684,  0.17254901960784313),
+    (0.0                ,  0.26666666666666666,  0.10588235294117647)
+    )
+
+_Greys_data = (
+    (1.0                ,  1.0                ,  1.0                ),
+    (0.94117647058823528,  0.94117647058823528,  0.94117647058823528),
+    (0.85098039215686272,  0.85098039215686272,  0.85098039215686272),
+    (0.74117647058823533,  0.74117647058823533,  0.74117647058823533),
+    (0.58823529411764708,  0.58823529411764708,  0.58823529411764708),
+    (0.45098039215686275,  0.45098039215686275,  0.45098039215686275),
+    (0.32156862745098042,  0.32156862745098042,  0.32156862745098042),
+    (0.14509803921568629,  0.14509803921568629,  0.14509803921568629),
+    (0.0                ,  0.0                ,  0.0                )
+    )
+
+_Oranges_data = (
+    (1.0                ,  0.96078431372549022,  0.92156862745098034),
+    (0.99607843137254903,  0.90196078431372551,  0.80784313725490198),
+    (0.99215686274509807,  0.81568627450980391,  0.63529411764705879),
+    (0.99215686274509807,  0.68235294117647061,  0.41960784313725491),
+    (0.99215686274509807,  0.55294117647058827,  0.23529411764705882),
+    (0.94509803921568625,  0.41176470588235292,  0.07450980392156863),
+    (0.85098039215686272,  0.28235294117647058,  0.00392156862745098),
+    (0.65098039215686276,  0.21176470588235294,  0.01176470588235294),
+    (0.49803921568627452,  0.15294117647058825,  0.01568627450980392)
+    )
+
+_OrRd_data = (
+    (1.0                ,  0.96862745098039216,  0.92549019607843142),
+    (0.99607843137254903,  0.90980392156862744,  0.78431372549019607),
+    (0.99215686274509807,  0.83137254901960789,  0.61960784313725492),
+    (0.99215686274509807,  0.73333333333333328,  0.51764705882352946),
+    (0.9882352941176471 ,  0.55294117647058827,  0.34901960784313724),
+    (0.93725490196078431,  0.396078431372549  ,  0.28235294117647058),
+    (0.84313725490196079,  0.18823529411764706,  0.12156862745098039),
+    (0.70196078431372544,  0.0                ,  0.0                ),
+    (0.49803921568627452,  0.0                ,  0.0                )
+    )
+
+_PiYG_data = (
+    (0.55686274509803924,  0.00392156862745098,  0.32156862745098042),
+    (0.77254901960784317,  0.10588235294117647,  0.49019607843137253),
+    (0.87058823529411766,  0.46666666666666667,  0.68235294117647061),
+    (0.94509803921568625,  0.71372549019607845,  0.85490196078431369),
+    (0.99215686274509807,  0.8784313725490196 ,  0.93725490196078431),
+    (0.96862745098039216,  0.96862745098039216,  0.96862745098039216),
+    (0.90196078431372551,  0.96078431372549022,  0.81568627450980391),
+    (0.72156862745098038,  0.88235294117647056,  0.52549019607843139),
+    (0.49803921568627452,  0.73725490196078436,  0.25490196078431371),
+    (0.30196078431372547,  0.5725490196078431 ,  0.12941176470588237),
+    (0.15294117647058825,  0.39215686274509803,  0.09803921568627451)
+    )
+
+_PRGn_data = (
+    (0.25098039215686274,  0.0                ,  0.29411764705882354),
+    (0.46274509803921571,  0.16470588235294117,  0.51372549019607838),
+    (0.6                ,  0.4392156862745098 ,  0.6705882352941176 ),
+    (0.76078431372549016,  0.6470588235294118 ,  0.81176470588235294),
+    (0.90588235294117647,  0.83137254901960789,  0.90980392156862744),
+    (0.96862745098039216,  0.96862745098039216,  0.96862745098039216),
+    (0.85098039215686272,  0.94117647058823528,  0.82745098039215681),
+    (0.65098039215686276,  0.85882352941176465,  0.62745098039215685),
+    (0.35294117647058826,  0.68235294117647061,  0.38039215686274508),
+    (0.10588235294117647,  0.47058823529411764,  0.21568627450980393),
+    (0.0                ,  0.26666666666666666,  0.10588235294117647)
+    )
+
+_PuBu_data = (
+    (1.0                ,  0.96862745098039216,  0.98431372549019602),
+    (0.92549019607843142,  0.90588235294117647,  0.94901960784313721),
+    (0.81568627450980391,  0.81960784313725488,  0.90196078431372551),
+    (0.65098039215686276,  0.74117647058823533,  0.85882352941176465),
+    (0.45490196078431372,  0.66274509803921566,  0.81176470588235294),
+    (0.21176470588235294,  0.56470588235294117,  0.75294117647058822),
+    (0.0196078431372549 ,  0.4392156862745098 ,  0.69019607843137254),
+    (0.01568627450980392,  0.35294117647058826,  0.55294117647058827),
+    (0.00784313725490196,  0.2196078431372549 ,  0.34509803921568627)
+    )
+
+_PuBuGn_data = (
+    (1.0                ,  0.96862745098039216,  0.98431372549019602),
+    (0.92549019607843142,  0.88627450980392153,  0.94117647058823528),
+    (0.81568627450980391,  0.81960784313725488,  0.90196078431372551),
+    (0.65098039215686276,  0.74117647058823533,  0.85882352941176465),
+    (0.40392156862745099,  0.66274509803921566,  0.81176470588235294),
+    (0.21176470588235294,  0.56470588235294117,  0.75294117647058822),
+    (0.00784313725490196,  0.50588235294117645,  0.54117647058823526),
+    (0.00392156862745098,  0.42352941176470588,  0.34901960784313724),
+    (0.00392156862745098,  0.27450980392156865,  0.21176470588235294)
+    )
+
+_PuOr_data = (
+    (0.49803921568627452,  0.23137254901960785,  0.03137254901960784),
+    (0.70196078431372544,  0.34509803921568627,  0.02352941176470588),
+    (0.8784313725490196 ,  0.50980392156862742,  0.07843137254901961),
+    (0.99215686274509807,  0.72156862745098038,  0.38823529411764707),
+    (0.99607843137254903,  0.8784313725490196 ,  0.71372549019607845),
+    (0.96862745098039216,  0.96862745098039216,  0.96862745098039216),
+    (0.84705882352941175,  0.85490196078431369,  0.92156862745098034),
+    (0.69803921568627447,  0.6705882352941176 ,  0.82352941176470584),
+    (0.50196078431372548,  0.45098039215686275,  0.67450980392156867),
+    (0.32941176470588235,  0.15294117647058825,  0.53333333333333333),
+    (0.17647058823529413,  0.0                ,  0.29411764705882354)
+    )
+
+_PuRd_data = (
+    (0.96862745098039216,  0.95686274509803926,  0.97647058823529409),
+    (0.90588235294117647,  0.88235294117647056,  0.93725490196078431),
+    (0.83137254901960789,  0.72549019607843135,  0.85490196078431369),
+    (0.78823529411764703,  0.58039215686274515,  0.7803921568627451 ),
+    (0.87450980392156863,  0.396078431372549  ,  0.69019607843137254),
+    (0.90588235294117647,  0.16078431372549021,  0.54117647058823526),
+    (0.80784313725490198,  0.07058823529411765,  0.33725490196078434),
+    (0.59607843137254901,  0.0                ,  0.2627450980392157 ),
+    (0.40392156862745099,  0.0                ,  0.12156862745098039)
+    )
+
+_Purples_data = (
+    (0.9882352941176471 ,  0.98431372549019602,  0.99215686274509807),
+    (0.93725490196078431,  0.92941176470588238,  0.96078431372549022),
+    (0.85490196078431369,  0.85490196078431369,  0.92156862745098034),
+    (0.73725490196078436,  0.74117647058823533,  0.86274509803921573),
+    (0.61960784313725492,  0.60392156862745094,  0.78431372549019607),
+    (0.50196078431372548,  0.49019607843137253,  0.72941176470588232),
+    (0.41568627450980394,  0.31764705882352939,  0.63921568627450975),
+    (0.32941176470588235,  0.15294117647058825,  0.5607843137254902 ),
+    (0.24705882352941178,  0.0                ,  0.49019607843137253)
+    )
+
+_RdBu_data = (
+    (0.40392156862745099,  0.0                ,  0.12156862745098039),
+    (0.69803921568627447,  0.09411764705882353,  0.16862745098039217),
+    (0.83921568627450982,  0.37647058823529411,  0.30196078431372547),
+    (0.95686274509803926,  0.6470588235294118 ,  0.50980392156862742),
+    (0.99215686274509807,  0.85882352941176465,  0.7803921568627451 ),
+    (0.96862745098039216,  0.96862745098039216,  0.96862745098039216),
+    (0.81960784313725488,  0.89803921568627454,  0.94117647058823528),
+    (0.5725490196078431 ,  0.77254901960784317,  0.87058823529411766),
+    (0.2627450980392157 ,  0.57647058823529407,  0.76470588235294112),
+    (0.12941176470588237,  0.4                ,  0.67450980392156867),
+    (0.0196078431372549 ,  0.18823529411764706,  0.38039215686274508)
+    )
+
+_RdGy_data = (
+    (0.40392156862745099,  0.0                ,  0.12156862745098039),
+    (0.69803921568627447,  0.09411764705882353,  0.16862745098039217),
+    (0.83921568627450982,  0.37647058823529411,  0.30196078431372547),
+    (0.95686274509803926,  0.6470588235294118 ,  0.50980392156862742),
+    (0.99215686274509807,  0.85882352941176465,  0.7803921568627451 ),
+    (1.0                ,  1.0                ,  1.0                ),
+    (0.8784313725490196 ,  0.8784313725490196 ,  0.8784313725490196 ),
+    (0.72941176470588232,  0.72941176470588232,  0.72941176470588232),
+    (0.52941176470588236,  0.52941176470588236,  0.52941176470588236),
+    (0.30196078431372547,  0.30196078431372547,  0.30196078431372547),
+    (0.10196078431372549,  0.10196078431372549,  0.10196078431372549)
+    )
+
+_RdPu_data = (
+    (1.0                ,  0.96862745098039216,  0.95294117647058818),
+    (0.99215686274509807,  0.8784313725490196 ,  0.86666666666666667),
+    (0.9882352941176471 ,  0.77254901960784317,  0.75294117647058822),
+    (0.98039215686274506,  0.62352941176470589,  0.70980392156862748),
+    (0.96862745098039216,  0.40784313725490196,  0.63137254901960782),
+    (0.86666666666666667,  0.20392156862745098,  0.59215686274509804),
+    (0.68235294117647061,  0.00392156862745098,  0.49411764705882355),
+    (0.47843137254901963,  0.00392156862745098,  0.46666666666666667),
+    (0.28627450980392155,  0.0                ,  0.41568627450980394)
+    )
+
+_RdYlBu_data = (
+    (0.6470588235294118 , 0.0                 , 0.14901960784313725),
+    (0.84313725490196079, 0.18823529411764706 , 0.15294117647058825),
+    (0.95686274509803926, 0.42745098039215684 , 0.2627450980392157 ),
+    (0.99215686274509807, 0.68235294117647061 , 0.38039215686274508),
+    (0.99607843137254903, 0.8784313725490196  , 0.56470588235294117),
+    (1.0                , 1.0                 , 0.74901960784313726),
+    (0.8784313725490196 , 0.95294117647058818 , 0.97254901960784312),
+    (0.6705882352941176 , 0.85098039215686272 , 0.9137254901960784 ),
+    (0.45490196078431372, 0.67843137254901964 , 0.81960784313725488),
+    (0.27058823529411763, 0.45882352941176469 , 0.70588235294117652),
+    (0.19215686274509805, 0.21176470588235294 , 0.58431372549019611)
+    )
+
+_RdYlGn_data = (
+    (0.6470588235294118 , 0.0                 , 0.14901960784313725),
+    (0.84313725490196079, 0.18823529411764706 , 0.15294117647058825),
+    (0.95686274509803926, 0.42745098039215684 , 0.2627450980392157 ),
+    (0.99215686274509807, 0.68235294117647061 , 0.38039215686274508),
+    (0.99607843137254903, 0.8784313725490196  , 0.54509803921568623),
+    (1.0                , 1.0                 , 0.74901960784313726),
+    (0.85098039215686272, 0.93725490196078431 , 0.54509803921568623),
+    (0.65098039215686276, 0.85098039215686272 , 0.41568627450980394),
+    (0.4                , 0.74117647058823533 , 0.38823529411764707),
+    (0.10196078431372549, 0.59607843137254901 , 0.31372549019607843),
+    (0.0                , 0.40784313725490196 , 0.21568627450980393)
+    )
+
+_Reds_data = (
+    (1.0                , 0.96078431372549022 , 0.94117647058823528),
+    (0.99607843137254903, 0.8784313725490196  , 0.82352941176470584),
+    (0.9882352941176471 , 0.73333333333333328 , 0.63137254901960782),
+    (0.9882352941176471 , 0.5725490196078431  , 0.44705882352941179),
+    (0.98431372549019602, 0.41568627450980394 , 0.29019607843137257),
+    (0.93725490196078431, 0.23137254901960785 , 0.17254901960784313),
+    (0.79607843137254897, 0.094117647058823528, 0.11372549019607843),
+    (0.6470588235294118 , 0.058823529411764705, 0.08235294117647058),
+    (0.40392156862745099, 0.0                 , 0.05098039215686274)
+    )
+
+_Spectral_data = (
+    (0.61960784313725492, 0.003921568627450980, 0.25882352941176473),
+    (0.83529411764705885, 0.24313725490196078 , 0.30980392156862746),
+    (0.95686274509803926, 0.42745098039215684 , 0.2627450980392157 ),
+    (0.99215686274509807, 0.68235294117647061 , 0.38039215686274508),
+    (0.99607843137254903, 0.8784313725490196  , 0.54509803921568623),
+    (1.0                , 1.0                 , 0.74901960784313726),
+    (0.90196078431372551, 0.96078431372549022 , 0.59607843137254901),
+    (0.6705882352941176 , 0.8666666666666667  , 0.64313725490196083),
+    (0.4                , 0.76078431372549016 , 0.6470588235294118 ),
+    (0.19607843137254902, 0.53333333333333333 , 0.74117647058823533),
+    (0.36862745098039218, 0.30980392156862746 , 0.63529411764705879)
+    )
+
+_YlGn_data = (
+    (1.0                , 1.0                 , 0.89803921568627454),
+    (0.96862745098039216, 0.9882352941176471  , 0.72549019607843135),
+    (0.85098039215686272, 0.94117647058823528 , 0.63921568627450975),
+    (0.67843137254901964, 0.8666666666666667  , 0.55686274509803924),
+    (0.47058823529411764, 0.77647058823529413 , 0.47450980392156861),
+    (0.25490196078431371, 0.6705882352941176  , 0.36470588235294116),
+    (0.13725490196078433, 0.51764705882352946 , 0.2627450980392157 ),
+    (0.0                , 0.40784313725490196 , 0.21568627450980393),
+    (0.0                , 0.27058823529411763 , 0.16078431372549021)
+    )
+
+_YlGnBu_data = (
+    (1.0                , 1.0                 , 0.85098039215686272),
+    (0.92941176470588238, 0.97254901960784312 , 0.69411764705882351),
+    (0.7803921568627451 , 0.9137254901960784  , 0.70588235294117652),
+    (0.49803921568627452, 0.80392156862745101 , 0.73333333333333328),
+    (0.25490196078431371, 0.71372549019607845 , 0.7686274509803922 ),
+    (0.11372549019607843, 0.56862745098039214 , 0.75294117647058822),
+    (0.13333333333333333, 0.36862745098039218 , 0.6588235294117647 ),
+    (0.14509803921568629, 0.20392156862745098 , 0.58039215686274515),
+    (0.03137254901960784, 0.11372549019607843 , 0.34509803921568627)
+    )
+
+_YlOrBr_data = (
+    (1.0                , 1.0                 , 0.89803921568627454),
+    (1.0                , 0.96862745098039216 , 0.73725490196078436),
+    (0.99607843137254903, 0.8901960784313725  , 0.56862745098039214),
+    (0.99607843137254903, 0.7686274509803922  , 0.30980392156862746),
+    (0.99607843137254903, 0.6                 , 0.16078431372549021),
+    (0.92549019607843142, 0.4392156862745098  , 0.07843137254901961),
+    (0.8                , 0.29803921568627451 , 0.00784313725490196),
+    (0.6                , 0.20392156862745098 , 0.01568627450980392),
+    (0.4                , 0.14509803921568629 , 0.02352941176470588)
+    )
+
+_YlOrRd_data = (
+    (1.0                , 1.0                 , 0.8                ),
+    (1.0                , 0.92941176470588238 , 0.62745098039215685),
+    (0.99607843137254903, 0.85098039215686272 , 0.46274509803921571),
+    (0.99607843137254903, 0.69803921568627447 , 0.29803921568627451),
+    (0.99215686274509807, 0.55294117647058827 , 0.23529411764705882),
+    (0.9882352941176471 , 0.30588235294117649 , 0.16470588235294117),
+    (0.8901960784313725 , 0.10196078431372549 , 0.10980392156862745),
+    (0.74117647058823533, 0.0                 , 0.14901960784313725),
+    (0.50196078431372548, 0.0                 , 0.14901960784313725)
+    )
+
+
+# ColorBrewer's qualitative maps, implemented using ListedColormap
+# for use with mpl.colors.NoNorm
+
+_Accent_data = (
+    (0.49803921568627452, 0.78823529411764703, 0.49803921568627452),
+    (0.74509803921568629, 0.68235294117647061, 0.83137254901960789),
+    (0.99215686274509807, 0.75294117647058822, 0.52549019607843139),
+    (1.0,                 1.0,                 0.6                ),
+    (0.2196078431372549,  0.42352941176470588, 0.69019607843137254),
+    (0.94117647058823528, 0.00784313725490196, 0.49803921568627452),
+    (0.74901960784313726, 0.35686274509803922, 0.09019607843137254),
+    (0.4,                 0.4,                 0.4                ),
+    )
+
+_Dark2_data = (
+    (0.10588235294117647, 0.61960784313725492, 0.46666666666666667),
+    (0.85098039215686272, 0.37254901960784315, 0.00784313725490196),
+    (0.45882352941176469, 0.4392156862745098,  0.70196078431372544),
+    (0.90588235294117647, 0.16078431372549021, 0.54117647058823526),
+    (0.4,                 0.65098039215686276, 0.11764705882352941),
+    (0.90196078431372551, 0.6705882352941176,  0.00784313725490196),
+    (0.65098039215686276, 0.46274509803921571, 0.11372549019607843),
+    (0.4,                 0.4,                 0.4                ),
+    )
+
+_Paired_data = (
+    (0.65098039215686276, 0.80784313725490198, 0.8901960784313725 ),
+    (0.12156862745098039, 0.47058823529411764, 0.70588235294117652),
+    (0.69803921568627447, 0.87450980392156863, 0.54117647058823526),
+    (0.2,                 0.62745098039215685, 0.17254901960784313),
+    (0.98431372549019602, 0.60392156862745094, 0.6                ),
+    (0.8901960784313725,  0.10196078431372549, 0.10980392156862745),
+    (0.99215686274509807, 0.74901960784313726, 0.43529411764705883),
+    (1.0,                 0.49803921568627452, 0.0                ),
+    (0.792156862745098,   0.69803921568627447, 0.83921568627450982),
+    (0.41568627450980394, 0.23921568627450981, 0.60392156862745094),
+    (1.0,                 1.0,                 0.6                ),
+    (0.69411764705882351, 0.34901960784313724, 0.15686274509803921),
+    )
+
+_Pastel1_data = (
+    (0.98431372549019602, 0.70588235294117652, 0.68235294117647061),
+    (0.70196078431372544, 0.80392156862745101, 0.8901960784313725 ),
+    (0.8,                 0.92156862745098034, 0.77254901960784317),
+    (0.87058823529411766, 0.79607843137254897, 0.89411764705882357),
+    (0.99607843137254903, 0.85098039215686272, 0.65098039215686276),
+    (1.0,                 1.0,                 0.8                ),
+    (0.89803921568627454, 0.84705882352941175, 0.74117647058823533),
+    (0.99215686274509807, 0.85490196078431369, 0.92549019607843142),
+    (0.94901960784313721, 0.94901960784313721, 0.94901960784313721),
+    )
+
+_Pastel2_data = (
+    (0.70196078431372544, 0.88627450980392153, 0.80392156862745101),
+    (0.99215686274509807, 0.80392156862745101, 0.67450980392156867),
+    (0.79607843137254897, 0.83529411764705885, 0.90980392156862744),
+    (0.95686274509803926, 0.792156862745098,   0.89411764705882357),
+    (0.90196078431372551, 0.96078431372549022, 0.78823529411764703),
+    (1.0,                 0.94901960784313721, 0.68235294117647061),
+    (0.94509803921568625, 0.88627450980392153, 0.8                ),
+    (0.8,                 0.8,                 0.8                ),
+    )
+
+_Set1_data = (
+    (0.89411764705882357, 0.10196078431372549, 0.10980392156862745),
+    (0.21568627450980393, 0.49411764705882355, 0.72156862745098038),
+    (0.30196078431372547, 0.68627450980392157, 0.29019607843137257),
+    (0.59607843137254901, 0.30588235294117649, 0.63921568627450975),
+    (1.0,                 0.49803921568627452, 0.0                ),
+    (1.0,                 1.0,                 0.2                ),
+    (0.65098039215686276, 0.33725490196078434, 0.15686274509803921),
+    (0.96862745098039216, 0.50588235294117645, 0.74901960784313726),
+    (0.6,                 0.6,                 0.6),
+    )
+
+_Set2_data = (
+    (0.4,                 0.76078431372549016, 0.6470588235294118 ),
+    (0.9882352941176471,  0.55294117647058827, 0.3843137254901961 ),
+    (0.55294117647058827, 0.62745098039215685, 0.79607843137254897),
+    (0.90588235294117647, 0.54117647058823526, 0.76470588235294112),
+    (0.65098039215686276, 0.84705882352941175, 0.32941176470588235),
+    (1.0,                 0.85098039215686272, 0.18431372549019609),
+    (0.89803921568627454, 0.7686274509803922,  0.58039215686274515),
+    (0.70196078431372544, 0.70196078431372544, 0.70196078431372544),
+    )
+
+_Set3_data = (
+    (0.55294117647058827, 0.82745098039215681, 0.7803921568627451 ),
+    (1.0,                 1.0,                 0.70196078431372544),
+    (0.74509803921568629, 0.72941176470588232, 0.85490196078431369),
+    (0.98431372549019602, 0.50196078431372548, 0.44705882352941179),
+    (0.50196078431372548, 0.69411764705882351, 0.82745098039215681),
+    (0.99215686274509807, 0.70588235294117652, 0.3843137254901961 ),
+    (0.70196078431372544, 0.87058823529411766, 0.41176470588235292),
+    (0.9882352941176471,  0.80392156862745101, 0.89803921568627454),
+    (0.85098039215686272, 0.85098039215686272, 0.85098039215686272),
+    (0.73725490196078436, 0.50196078431372548, 0.74117647058823533),
+    (0.8,                 0.92156862745098034, 0.77254901960784317),
+    (1.0,                 0.92941176470588238, 0.43529411764705883),
+    )
+
+
+# The next 7 palettes are from the Yorick scientific visualization package,
+# an evolution of the GIST package, both by David H. Munro.
+# They are released under a BSD-like license (see LICENSE_YORICK in
+# the license directory of the matplotlib source distribution).
+#
+# Most palette functions have been reduced to simple function descriptions
+# by Reinier Heeres, since the rgb components were mostly straight lines.
+# gist_earth_data and gist_ncar_data were simplified by a script and some
+# manual effort.
+
+_gist_earth_data = \
+{'red': (
+(0.0, 0.0, 0.0000),
+(0.2824, 0.1882, 0.1882),
+(0.4588, 0.2714, 0.2714),
+(0.5490, 0.4719, 0.4719),
+(0.6980, 0.7176, 0.7176),
+(0.7882, 0.7553, 0.7553),
+(1.0000, 0.9922, 0.9922),
+), 'green': (
+(0.0, 0.0, 0.0000),
+(0.0275, 0.0000, 0.0000),
+(0.1098, 0.1893, 0.1893),
+(0.1647, 0.3035, 0.3035),
+(0.2078, 0.3841, 0.3841),
+(0.2824, 0.5020, 0.5020),
+(0.5216, 0.6397, 0.6397),
+(0.6980, 0.7171, 0.7171),
+(0.7882, 0.6392, 0.6392),
+(0.7922, 0.6413, 0.6413),
+(0.8000, 0.6447, 0.6447),
+(0.8078, 0.6481, 0.6481),
+(0.8157, 0.6549, 0.6549),
+(0.8667, 0.6991, 0.6991),
+(0.8745, 0.7103, 0.7103),
+(0.8824, 0.7216, 0.7216),
+(0.8902, 0.7323, 0.7323),
+(0.8980, 0.7430, 0.7430),
+(0.9412, 0.8275, 0.8275),
+(0.9569, 0.8635, 0.8635),
+(0.9647, 0.8816, 0.8816),
+(0.9961, 0.9733, 0.9733),
+(1.0000, 0.9843, 0.9843),
+), 'blue': (
+(0.0, 0.0, 0.0000),
+(0.0039, 0.1684, 0.1684),
+(0.0078, 0.2212, 0.2212),
+(0.0275, 0.4329, 0.4329),
+(0.0314, 0.4549, 0.4549),
+(0.2824, 0.5004, 0.5004),
+(0.4667, 0.2748, 0.2748),
+(0.5451, 0.3205, 0.3205),
+(0.7843, 0.3961, 0.3961),
+(0.8941, 0.6651, 0.6651),
+(1.0000, 0.9843, 0.9843),
+)}
+
+_gist_gray_data = {
+        'red': gfunc[3],
+        'green': gfunc[3],
+        'blue': gfunc[3],
+}
+
+def _gist_heat_red(x): return 1.5 * x
+def _gist_heat_green(x): return 2 * x - 1
+def _gist_heat_blue(x): return 4 * x - 3
+_gist_heat_data = {
+    'red': _gist_heat_red, 'green': _gist_heat_green, 'blue': _gist_heat_blue}
+
+_gist_ncar_data = \
+{'red': (
+(0.0, 0.0, 0.0000),
+(0.3098, 0.0000, 0.0000),
+(0.3725, 0.3993, 0.3993),
+(0.4235, 0.5003, 0.5003),
+(0.5333, 1.0000, 1.0000),
+(0.7922, 1.0000, 1.0000),
+(0.8471, 0.6218, 0.6218),
+(0.8980, 0.9235, 0.9235),
+(1.0000, 0.9961, 0.9961),
+), 'green': (
+(0.0, 0.0, 0.0000),
+(0.0510, 0.3722, 0.3722),
+(0.1059, 0.0000, 0.0000),
+(0.1569, 0.7202, 0.7202),
+(0.1608, 0.7537, 0.7537),
+(0.1647, 0.7752, 0.7752),
+(0.2157, 1.0000, 1.0000),
+(0.2588, 0.9804, 0.9804),
+(0.2706, 0.9804, 0.9804),
+(0.3176, 1.0000, 1.0000),
+(0.3686, 0.8081, 0.8081),
+(0.4275, 1.0000, 1.0000),
+(0.5216, 1.0000, 1.0000),
+(0.6314, 0.7292, 0.7292),
+(0.6863, 0.2796, 0.2796),
+(0.7451, 0.0000, 0.0000),
+(0.7922, 0.0000, 0.0000),
+(0.8431, 0.1753, 0.1753),
+(0.8980, 0.5000, 0.5000),
+(1.0000, 0.9725, 0.9725),
+), 'blue': (
+(0.0, 0.5020, 0.5020),
+(0.0510, 0.0222, 0.0222),
+(0.1098, 1.0000, 1.0000),
+(0.2039, 1.0000, 1.0000),
+(0.2627, 0.6145, 0.6145),
+(0.3216, 0.0000, 0.0000),
+(0.4157, 0.0000, 0.0000),
+(0.4745, 0.2342, 0.2342),
+(0.5333, 0.0000, 0.0000),
+(0.5804, 0.0000, 0.0000),
+(0.6314, 0.0549, 0.0549),
+(0.6902, 0.0000, 0.0000),
+(0.7373, 0.0000, 0.0000),
+(0.7922, 0.9738, 0.9738),
+(0.8000, 1.0000, 1.0000),
+(0.8431, 1.0000, 1.0000),
+(0.8980, 0.9341, 0.9341),
+(1.0000, 0.9961, 0.9961),
+)}
+
+_gist_rainbow_data = (
+        (0.000, (1.00, 0.00, 0.16)),
+        (0.030, (1.00, 0.00, 0.00)),
+        (0.215, (1.00, 1.00, 0.00)),
+        (0.400, (0.00, 1.00, 0.00)),
+        (0.586, (0.00, 1.00, 1.00)),
+        (0.770, (0.00, 0.00, 1.00)),
+        (0.954, (1.00, 0.00, 1.00)),
+        (1.000, (1.00, 0.00, 0.75))
+)
+
+_gist_stern_data = {
+        'red': (
+            (0.000, 0.000, 0.000), (0.0547, 1.000, 1.000),
+            (0.250, 0.027, 0.250),  # (0.2500, 0.250, 0.250),
+            (1.000, 1.000, 1.000)),
+        'green': ((0, 0, 0), (1, 1, 1)),
+        'blue': (
+            (0.000, 0.000, 0.000), (0.500, 1.000, 1.000),
+            (0.735, 0.000, 0.000), (1.000, 1.000, 1.000))
+}
+
+def _gist_yarg(x): return 1 - x
+_gist_yarg_data = {'red': _gist_yarg, 'green': _gist_yarg, 'blue': _gist_yarg}
+
+# This bipolar color map was generated from CoolWarmFloat33.csv of
+# "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland.
+# <http://www.kennethmoreland.com/color-maps/>
+_coolwarm_data = {
+    'red': [
+        (0.0, 0.2298057, 0.2298057),
+        (0.03125, 0.26623388, 0.26623388),
+        (0.0625, 0.30386891, 0.30386891),
+        (0.09375, 0.342804478, 0.342804478),
+        (0.125, 0.38301334, 0.38301334),
+        (0.15625, 0.424369608, 0.424369608),
+        (0.1875, 0.46666708, 0.46666708),
+        (0.21875, 0.509635204, 0.509635204),
+        (0.25, 0.552953156, 0.552953156),
+        (0.28125, 0.596262162, 0.596262162),
+        (0.3125, 0.639176211, 0.639176211),
+        (0.34375, 0.681291281, 0.681291281),
+        (0.375, 0.722193294, 0.722193294),
+        (0.40625, 0.761464949, 0.761464949),
+        (0.4375, 0.798691636, 0.798691636),
+        (0.46875, 0.833466556, 0.833466556),
+        (0.5, 0.865395197, 0.865395197),
+        (0.53125, 0.897787179, 0.897787179),
+        (0.5625, 0.924127593, 0.924127593),
+        (0.59375, 0.944468518, 0.944468518),
+        (0.625, 0.958852946, 0.958852946),
+        (0.65625, 0.96732803, 0.96732803),
+        (0.6875, 0.969954137, 0.969954137),
+        (0.71875, 0.966811177, 0.966811177),
+        (0.75, 0.958003065, 0.958003065),
+        (0.78125, 0.943660866, 0.943660866),
+        (0.8125, 0.923944917, 0.923944917),
+        (0.84375, 0.89904617, 0.89904617),
+        (0.875, 0.869186849, 0.869186849),
+        (0.90625, 0.834620542, 0.834620542),
+        (0.9375, 0.795631745, 0.795631745),
+        (0.96875, 0.752534934, 0.752534934),
+        (1.0, 0.705673158, 0.705673158)],
+    'green': [
+        (0.0, 0.298717966, 0.298717966),
+        (0.03125, 0.353094838, 0.353094838),
+        (0.0625, 0.406535296, 0.406535296),
+        (0.09375, 0.458757618, 0.458757618),
+        (0.125, 0.50941904, 0.50941904),
+        (0.15625, 0.558148092, 0.558148092),
+        (0.1875, 0.604562568, 0.604562568),
+        (0.21875, 0.648280772, 0.648280772),
+        (0.25, 0.688929332, 0.688929332),
+        (0.28125, 0.726149107, 0.726149107),
+        (0.3125, 0.759599947, 0.759599947),
+        (0.34375, 0.788964712, 0.788964712),
+        (0.375, 0.813952739, 0.813952739),
+        (0.40625, 0.834302879, 0.834302879),
+        (0.4375, 0.849786142, 0.849786142),
+        (0.46875, 0.860207984, 0.860207984),
+        (0.5, 0.86541021, 0.86541021),
+        (0.53125, 0.848937047, 0.848937047),
+        (0.5625, 0.827384882, 0.827384882),
+        (0.59375, 0.800927443, 0.800927443),
+        (0.625, 0.769767752, 0.769767752),
+        (0.65625, 0.734132809, 0.734132809),
+        (0.6875, 0.694266682, 0.694266682),
+        (0.71875, 0.650421156, 0.650421156),
+        (0.75, 0.602842431, 0.602842431),
+        (0.78125, 0.551750968, 0.551750968),
+        (0.8125, 0.49730856, 0.49730856),
+        (0.84375, 0.439559467, 0.439559467),
+        (0.875, 0.378313092, 0.378313092),
+        (0.90625, 0.312874446, 0.312874446),
+        (0.9375, 0.24128379, 0.24128379),
+        (0.96875, 0.157246067, 0.157246067),
+        (1.0, 0.01555616, 0.01555616)],
+    'blue': [
+        (0.0, 0.753683153, 0.753683153),
+        (0.03125, 0.801466763, 0.801466763),
+        (0.0625, 0.84495867, 0.84495867),
+        (0.09375, 0.883725899, 0.883725899),
+        (0.125, 0.917387822, 0.917387822),
+        (0.15625, 0.945619588, 0.945619588),
+        (0.1875, 0.968154911, 0.968154911),
+        (0.21875, 0.98478814, 0.98478814),
+        (0.25, 0.995375608, 0.995375608),
+        (0.28125, 0.999836203, 0.999836203),
+        (0.3125, 0.998151185, 0.998151185),
+        (0.34375, 0.990363227, 0.990363227),
+        (0.375, 0.976574709, 0.976574709),
+        (0.40625, 0.956945269, 0.956945269),
+        (0.4375, 0.931688648, 0.931688648),
+        (0.46875, 0.901068838, 0.901068838),
+        (0.5, 0.865395561, 0.865395561),
+        (0.53125, 0.820880546, 0.820880546),
+        (0.5625, 0.774508472, 0.774508472),
+        (0.59375, 0.726736146, 0.726736146),
+        (0.625, 0.678007945, 0.678007945),
+        (0.65625, 0.628751763, 0.628751763),
+        (0.6875, 0.579375448, 0.579375448),
+        (0.71875, 0.530263762, 0.530263762),
+        (0.75, 0.481775914, 0.481775914),
+        (0.78125, 0.434243684, 0.434243684),
+        (0.8125, 0.387970225, 0.387970225),
+        (0.84375, 0.343229596, 0.343229596),
+        (0.875, 0.300267182, 0.300267182),
+        (0.90625, 0.259301199, 0.259301199),
+        (0.9375, 0.220525627, 0.220525627),
+        (0.96875, 0.184115123, 0.184115123),
+        (1.0, 0.150232812, 0.150232812)]
+    }
+
+# Implementation of Carey Rappaport's CMRmap.
+# See `A Color Map for Effective Black-and-White Rendering of Color-Scale
+# Images' by Carey Rappaport
+# http://www.mathworks.com/matlabcentral/fileexchange/2662-cmrmap-m
+_CMRmap_data = {'red':     ((0.000, 0.00, 0.00),
+                           (0.125, 0.15, 0.15),
+                           (0.250, 0.30, 0.30),
+                           (0.375, 0.60, 0.60),
+                           (0.500, 1.00, 1.00),
+                           (0.625, 0.90, 0.90),
+                           (0.750, 0.90, 0.90),
+                           (0.875, 0.90, 0.90),
+                           (1.000, 1.00, 1.00)),
+                'green':  ((0.000, 0.00, 0.00),
+                           (0.125, 0.15, 0.15),
+                           (0.250, 0.15, 0.15),
+                           (0.375, 0.20, 0.20),
+                           (0.500, 0.25, 0.25),
+                           (0.625, 0.50, 0.50),
+                           (0.750, 0.75, 0.75),
+                           (0.875, 0.90, 0.90),
+                           (1.000, 1.00, 1.00)),
+                'blue':   ((0.000, 0.00, 0.00),
+                           (0.125, 0.50, 0.50),
+                           (0.250, 0.75, 0.75),
+                           (0.375, 0.50, 0.50),
+                           (0.500, 0.15, 0.15),
+                           (0.625, 0.00, 0.00),
+                           (0.750, 0.10, 0.10),
+                           (0.875, 0.50, 0.50),
+                           (1.000, 1.00, 1.00))}
+
+
+# An MIT licensed, colorblind-friendly heatmap from Wistia:
+#   https://github.com/wistia/heatmap-palette
+#   http://wistia.com/blog/heatmaps-for-colorblindness
+#
+# >>> import matplotlib.colors as c
+# >>> colors = ["#e4ff7a", "#ffe81a", "#ffbd00", "#ffa000", "#fc7f00"]
+# >>> cm = c.LinearSegmentedColormap.from_list('wistia', colors)
+# >>> _wistia_data = cm._segmentdata
+# >>> del _wistia_data['alpha']
+#
+_wistia_data = {
+    'red': [(0.0, 0.8941176470588236, 0.8941176470588236),
+            (0.25, 1.0, 1.0),
+            (0.5, 1.0, 1.0),
+            (0.75, 1.0, 1.0),
+            (1.0, 0.9882352941176471, 0.9882352941176471)],
+    'green': [(0.0, 1.0, 1.0),
+              (0.25, 0.9098039215686274, 0.9098039215686274),
+              (0.5, 0.7411764705882353, 0.7411764705882353),
+              (0.75, 0.6274509803921569, 0.6274509803921569),
+              (1.0, 0.4980392156862745, 0.4980392156862745)],
+    'blue': [(0.0, 0.47843137254901963, 0.47843137254901963),
+             (0.25, 0.10196078431372549, 0.10196078431372549),
+             (0.5, 0.0, 0.0),
+             (0.75, 0.0, 0.0),
+             (1.0, 0.0, 0.0)],
+}
+
+
+# Categorical palettes from Vega:
+# https://github.com/vega/vega/wiki/Scales
+# (divided by 255)
+#
+
+_tab10_data = (
+    (0.12156862745098039, 0.4666666666666667,  0.7058823529411765  ),  # 1f77b4
+    (1.0,                 0.4980392156862745,  0.054901960784313725),  # ff7f0e
+    (0.17254901960784313, 0.6274509803921569,  0.17254901960784313 ),  # 2ca02c
+    (0.8392156862745098,  0.15294117647058825, 0.1568627450980392  ),  # d62728
+    (0.5803921568627451,  0.403921568627451,   0.7411764705882353  ),  # 9467bd
+    (0.5490196078431373,  0.33725490196078434, 0.29411764705882354 ),  # 8c564b
+    (0.8901960784313725,  0.4666666666666667,  0.7607843137254902  ),  # e377c2
+    (0.4980392156862745,  0.4980392156862745,  0.4980392156862745  ),  # 7f7f7f
+    (0.7372549019607844,  0.7411764705882353,  0.13333333333333333 ),  # bcbd22
+    (0.09019607843137255, 0.7450980392156863,  0.8117647058823529),    # 17becf
+)
+
+_tab20_data = (
+    (0.12156862745098039, 0.4666666666666667,  0.7058823529411765  ),  # 1f77b4
+    (0.6823529411764706,  0.7803921568627451,  0.9098039215686274  ),  # aec7e8
+    (1.0,                 0.4980392156862745,  0.054901960784313725),  # ff7f0e
+    (1.0,                 0.7333333333333333,  0.47058823529411764 ),  # ffbb78
+    (0.17254901960784313, 0.6274509803921569,  0.17254901960784313 ),  # 2ca02c
+    (0.596078431372549,   0.8745098039215686,  0.5411764705882353  ),  # 98df8a
+    (0.8392156862745098,  0.15294117647058825, 0.1568627450980392  ),  # d62728
+    (1.0,                 0.596078431372549,   0.5882352941176471  ),  # ff9896
+    (0.5803921568627451,  0.403921568627451,   0.7411764705882353  ),  # 9467bd
+    (0.7725490196078432,  0.6901960784313725,  0.8352941176470589  ),  # c5b0d5
+    (0.5490196078431373,  0.33725490196078434, 0.29411764705882354 ),  # 8c564b
+    (0.7686274509803922,  0.611764705882353,   0.5803921568627451  ),  # c49c94
+    (0.8901960784313725,  0.4666666666666667,  0.7607843137254902  ),  # e377c2
+    (0.9686274509803922,  0.7137254901960784,  0.8235294117647058  ),  # f7b6d2
+    (0.4980392156862745,  0.4980392156862745,  0.4980392156862745  ),  # 7f7f7f
+    (0.7803921568627451,  0.7803921568627451,  0.7803921568627451  ),  # c7c7c7
+    (0.7372549019607844,  0.7411764705882353,  0.13333333333333333 ),  # bcbd22
+    (0.8588235294117647,  0.8588235294117647,  0.5529411764705883  ),  # dbdb8d
+    (0.09019607843137255, 0.7450980392156863,  0.8117647058823529  ),  # 17becf
+    (0.6196078431372549,  0.8549019607843137,  0.8980392156862745),    # 9edae5
+)
+
+_tab20b_data = (
+    (0.2235294117647059,  0.23137254901960785, 0.4745098039215686 ),  # 393b79
+    (0.3215686274509804,  0.32941176470588235, 0.6392156862745098 ),  # 5254a3
+    (0.4196078431372549,  0.43137254901960786, 0.8117647058823529 ),  # 6b6ecf
+    (0.611764705882353,   0.6196078431372549,  0.8705882352941177 ),  # 9c9ede
+    (0.38823529411764707, 0.4745098039215686,  0.2235294117647059 ),  # 637939
+    (0.5490196078431373,  0.6352941176470588,  0.3215686274509804 ),  # 8ca252
+    (0.7098039215686275,  0.8117647058823529,  0.4196078431372549 ),  # b5cf6b
+    (0.807843137254902,   0.8588235294117647,  0.611764705882353  ),  # cedb9c
+    (0.5490196078431373,  0.42745098039215684, 0.19215686274509805),  # 8c6d31
+    (0.7411764705882353,  0.6196078431372549,  0.2235294117647059 ),  # bd9e39
+    (0.9058823529411765,  0.7294117647058823,  0.3215686274509804 ),  # e7ba52
+    (0.9058823529411765,  0.796078431372549,   0.5803921568627451 ),  # e7cb94
+    (0.5176470588235295,  0.23529411764705882, 0.2235294117647059 ),  # 843c39
+    (0.6784313725490196,  0.28627450980392155, 0.2901960784313726 ),  # ad494a
+    (0.8392156862745098,  0.3803921568627451,  0.4196078431372549 ),  # d6616b
+    (0.9058823529411765,  0.5882352941176471,  0.611764705882353  ),  # e7969c
+    (0.4823529411764706,  0.2549019607843137,  0.45098039215686275),  # 7b4173
+    (0.6470588235294118,  0.3176470588235294,  0.5803921568627451 ),  # a55194
+    (0.807843137254902,   0.42745098039215684, 0.7411764705882353 ),  # ce6dbd
+    (0.8705882352941177,  0.6196078431372549,  0.8392156862745098 ),  # de9ed6
+)
+
+_tab20c_data = (
+    (0.19215686274509805, 0.5098039215686274,  0.7411764705882353  ),  # 3182bd
+    (0.4196078431372549,  0.6823529411764706,  0.8392156862745098  ),  # 6baed6
+    (0.6196078431372549,  0.792156862745098,   0.8823529411764706  ),  # 9ecae1
+    (0.7764705882352941,  0.8588235294117647,  0.9372549019607843  ),  # c6dbef
+    (0.9019607843137255,  0.3333333333333333,  0.050980392156862744),  # e6550d
+    (0.9921568627450981,  0.5529411764705883,  0.23529411764705882 ),  # fd8d3c
+    (0.9921568627450981,  0.6823529411764706,  0.4196078431372549  ),  # fdae6b
+    (0.9921568627450981,  0.8156862745098039,  0.6352941176470588  ),  # fdd0a2
+    (0.19215686274509805, 0.6392156862745098,  0.32941176470588235 ),  # 31a354
+    (0.4549019607843137,  0.7686274509803922,  0.4627450980392157  ),  # 74c476
+    (0.6313725490196078,  0.8509803921568627,  0.6078431372549019  ),  # a1d99b
+    (0.7803921568627451,  0.9137254901960784,  0.7529411764705882  ),  # c7e9c0
+    (0.4588235294117647,  0.4196078431372549,  0.6941176470588235  ),  # 756bb1
+    (0.6196078431372549,  0.6039215686274509,  0.7843137254901961  ),  # 9e9ac8
+    (0.7372549019607844,  0.7411764705882353,  0.8627450980392157  ),  # bcbddc
+    (0.8549019607843137,  0.8549019607843137,  0.9215686274509803  ),  # dadaeb
+    (0.38823529411764707, 0.38823529411764707, 0.38823529411764707 ),  # 636363
+    (0.5882352941176471,  0.5882352941176471,  0.5882352941176471  ),  # 969696
+    (0.7411764705882353,  0.7411764705882353,  0.7411764705882353  ),  # bdbdbd
+    (0.8509803921568627,  0.8509803921568627,  0.8509803921568627  ),  # d9d9d9
+)
+
+
+datad = {
+    'Blues': _Blues_data,
+    'BrBG': _BrBG_data,
+    'BuGn': _BuGn_data,
+    'BuPu': _BuPu_data,
+    'CMRmap': _CMRmap_data,
+    'GnBu': _GnBu_data,
+    'Greens': _Greens_data,
+    'Greys': _Greys_data,
+    'OrRd': _OrRd_data,
+    'Oranges': _Oranges_data,
+    'PRGn': _PRGn_data,
+    'PiYG': _PiYG_data,
+    'PuBu': _PuBu_data,
+    'PuBuGn': _PuBuGn_data,
+    'PuOr': _PuOr_data,
+    'PuRd': _PuRd_data,
+    'Purples': _Purples_data,
+    'RdBu': _RdBu_data,
+    'RdGy': _RdGy_data,
+    'RdPu': _RdPu_data,
+    'RdYlBu': _RdYlBu_data,
+    'RdYlGn': _RdYlGn_data,
+    'Reds': _Reds_data,
+    'Spectral': _Spectral_data,
+    'Wistia': _wistia_data,
+    'YlGn': _YlGn_data,
+    'YlGnBu': _YlGnBu_data,
+    'YlOrBr': _YlOrBr_data,
+    'YlOrRd': _YlOrRd_data,
+    'afmhot': _afmhot_data,
+    'autumn': _autumn_data,
+    'binary': _binary_data,
+    'bone': _bone_data,
+    'brg': _brg_data,
+    'bwr': _bwr_data,
+    'cool': _cool_data,
+    'coolwarm': _coolwarm_data,
+    'copper': _copper_data,
+    'cubehelix': _cubehelix_data,
+    'flag': _flag_data,
+    'gist_earth': _gist_earth_data,
+    'gist_gray': _gist_gray_data,
+    'gist_heat': _gist_heat_data,
+    'gist_ncar': _gist_ncar_data,
+    'gist_rainbow': _gist_rainbow_data,
+    'gist_stern': _gist_stern_data,
+    'gist_yarg': _gist_yarg_data,
+    'gnuplot': _gnuplot_data,
+    'gnuplot2': _gnuplot2_data,
+    'gray': _gray_data,
+    'hot': _hot_data,
+    'hsv': _hsv_data,
+    'jet': _jet_data,
+    'nipy_spectral': _nipy_spectral_data,
+    'ocean': _ocean_data,
+    'pink': _pink_data,
+    'prism': _prism_data,
+    'rainbow': _rainbow_data,
+    'seismic': _seismic_data,
+    'spring': _spring_data,
+    'summer': _summer_data,
+    'terrain': _terrain_data,
+    'winter': _winter_data,
+    # Qualitative
+    'Accent': {'listed': _Accent_data},
+    'Dark2': {'listed': _Dark2_data},
+    'Paired': {'listed': _Paired_data},
+    'Pastel1': {'listed': _Pastel1_data},
+    'Pastel2': {'listed': _Pastel2_data},
+    'Set1': {'listed': _Set1_data},
+    'Set2': {'listed': _Set2_data},
+    'Set3': {'listed': _Set3_data},
+    'tab10': {'listed': _tab10_data},
+    'tab20': {'listed': _tab20_data},
+    'tab20b': {'listed': _tab20b_data},
+    'tab20c': {'listed': _tab20c_data},
+}

+ 1813 - 0
venv/lib/python3.8/site-packages/matplotlib/_cm_listed.py

@@ -0,0 +1,1813 @@
+from .colors import ListedColormap
+
+_magma_data = [[0.001462, 0.000466, 0.013866],
+               [0.002258, 0.001295, 0.018331],
+               [0.003279, 0.002305, 0.023708],
+               [0.004512, 0.003490, 0.029965],
+               [0.005950, 0.004843, 0.037130],
+               [0.007588, 0.006356, 0.044973],
+               [0.009426, 0.008022, 0.052844],
+               [0.011465, 0.009828, 0.060750],
+               [0.013708, 0.011771, 0.068667],
+               [0.016156, 0.013840, 0.076603],
+               [0.018815, 0.016026, 0.084584],
+               [0.021692, 0.018320, 0.092610],
+               [0.024792, 0.020715, 0.100676],
+               [0.028123, 0.023201, 0.108787],
+               [0.031696, 0.025765, 0.116965],
+               [0.035520, 0.028397, 0.125209],
+               [0.039608, 0.031090, 0.133515],
+               [0.043830, 0.033830, 0.141886],
+               [0.048062, 0.036607, 0.150327],
+               [0.052320, 0.039407, 0.158841],
+               [0.056615, 0.042160, 0.167446],
+               [0.060949, 0.044794, 0.176129],
+               [0.065330, 0.047318, 0.184892],
+               [0.069764, 0.049726, 0.193735],
+               [0.074257, 0.052017, 0.202660],
+               [0.078815, 0.054184, 0.211667],
+               [0.083446, 0.056225, 0.220755],
+               [0.088155, 0.058133, 0.229922],
+               [0.092949, 0.059904, 0.239164],
+               [0.097833, 0.061531, 0.248477],
+               [0.102815, 0.063010, 0.257854],
+               [0.107899, 0.064335, 0.267289],
+               [0.113094, 0.065492, 0.276784],
+               [0.118405, 0.066479, 0.286321],
+               [0.123833, 0.067295, 0.295879],
+               [0.129380, 0.067935, 0.305443],
+               [0.135053, 0.068391, 0.315000],
+               [0.140858, 0.068654, 0.324538],
+               [0.146785, 0.068738, 0.334011],
+               [0.152839, 0.068637, 0.343404],
+               [0.159018, 0.068354, 0.352688],
+               [0.165308, 0.067911, 0.361816],
+               [0.171713, 0.067305, 0.370771],
+               [0.178212, 0.066576, 0.379497],
+               [0.184801, 0.065732, 0.387973],
+               [0.191460, 0.064818, 0.396152],
+               [0.198177, 0.063862, 0.404009],
+               [0.204935, 0.062907, 0.411514],
+               [0.211718, 0.061992, 0.418647],
+               [0.218512, 0.061158, 0.425392],
+               [0.225302, 0.060445, 0.431742],
+               [0.232077, 0.059889, 0.437695],
+               [0.238826, 0.059517, 0.443256],
+               [0.245543, 0.059352, 0.448436],
+               [0.252220, 0.059415, 0.453248],
+               [0.258857, 0.059706, 0.457710],
+               [0.265447, 0.060237, 0.461840],
+               [0.271994, 0.060994, 0.465660],
+               [0.278493, 0.061978, 0.469190],
+               [0.284951, 0.063168, 0.472451],
+               [0.291366, 0.064553, 0.475462],
+               [0.297740, 0.066117, 0.478243],
+               [0.304081, 0.067835, 0.480812],
+               [0.310382, 0.069702, 0.483186],
+               [0.316654, 0.071690, 0.485380],
+               [0.322899, 0.073782, 0.487408],
+               [0.329114, 0.075972, 0.489287],
+               [0.335308, 0.078236, 0.491024],
+               [0.341482, 0.080564, 0.492631],
+               [0.347636, 0.082946, 0.494121],
+               [0.353773, 0.085373, 0.495501],
+               [0.359898, 0.087831, 0.496778],
+               [0.366012, 0.090314, 0.497960],
+               [0.372116, 0.092816, 0.499053],
+               [0.378211, 0.095332, 0.500067],
+               [0.384299, 0.097855, 0.501002],
+               [0.390384, 0.100379, 0.501864],
+               [0.396467, 0.102902, 0.502658],
+               [0.402548, 0.105420, 0.503386],
+               [0.408629, 0.107930, 0.504052],
+               [0.414709, 0.110431, 0.504662],
+               [0.420791, 0.112920, 0.505215],
+               [0.426877, 0.115395, 0.505714],
+               [0.432967, 0.117855, 0.506160],
+               [0.439062, 0.120298, 0.506555],
+               [0.445163, 0.122724, 0.506901],
+               [0.451271, 0.125132, 0.507198],
+               [0.457386, 0.127522, 0.507448],
+               [0.463508, 0.129893, 0.507652],
+               [0.469640, 0.132245, 0.507809],
+               [0.475780, 0.134577, 0.507921],
+               [0.481929, 0.136891, 0.507989],
+               [0.488088, 0.139186, 0.508011],
+               [0.494258, 0.141462, 0.507988],
+               [0.500438, 0.143719, 0.507920],
+               [0.506629, 0.145958, 0.507806],
+               [0.512831, 0.148179, 0.507648],
+               [0.519045, 0.150383, 0.507443],
+               [0.525270, 0.152569, 0.507192],
+               [0.531507, 0.154739, 0.506895],
+               [0.537755, 0.156894, 0.506551],
+               [0.544015, 0.159033, 0.506159],
+               [0.550287, 0.161158, 0.505719],
+               [0.556571, 0.163269, 0.505230],
+               [0.562866, 0.165368, 0.504692],
+               [0.569172, 0.167454, 0.504105],
+               [0.575490, 0.169530, 0.503466],
+               [0.581819, 0.171596, 0.502777],
+               [0.588158, 0.173652, 0.502035],
+               [0.594508, 0.175701, 0.501241],
+               [0.600868, 0.177743, 0.500394],
+               [0.607238, 0.179779, 0.499492],
+               [0.613617, 0.181811, 0.498536],
+               [0.620005, 0.183840, 0.497524],
+               [0.626401, 0.185867, 0.496456],
+               [0.632805, 0.187893, 0.495332],
+               [0.639216, 0.189921, 0.494150],
+               [0.645633, 0.191952, 0.492910],
+               [0.652056, 0.193986, 0.491611],
+               [0.658483, 0.196027, 0.490253],
+               [0.664915, 0.198075, 0.488836],
+               [0.671349, 0.200133, 0.487358],
+               [0.677786, 0.202203, 0.485819],
+               [0.684224, 0.204286, 0.484219],
+               [0.690661, 0.206384, 0.482558],
+               [0.697098, 0.208501, 0.480835],
+               [0.703532, 0.210638, 0.479049],
+               [0.709962, 0.212797, 0.477201],
+               [0.716387, 0.214982, 0.475290],
+               [0.722805, 0.217194, 0.473316],
+               [0.729216, 0.219437, 0.471279],
+               [0.735616, 0.221713, 0.469180],
+               [0.742004, 0.224025, 0.467018],
+               [0.748378, 0.226377, 0.464794],
+               [0.754737, 0.228772, 0.462509],
+               [0.761077, 0.231214, 0.460162],
+               [0.767398, 0.233705, 0.457755],
+               [0.773695, 0.236249, 0.455289],
+               [0.779968, 0.238851, 0.452765],
+               [0.786212, 0.241514, 0.450184],
+               [0.792427, 0.244242, 0.447543],
+               [0.798608, 0.247040, 0.444848],
+               [0.804752, 0.249911, 0.442102],
+               [0.810855, 0.252861, 0.439305],
+               [0.816914, 0.255895, 0.436461],
+               [0.822926, 0.259016, 0.433573],
+               [0.828886, 0.262229, 0.430644],
+               [0.834791, 0.265540, 0.427671],
+               [0.840636, 0.268953, 0.424666],
+               [0.846416, 0.272473, 0.421631],
+               [0.852126, 0.276106, 0.418573],
+               [0.857763, 0.279857, 0.415496],
+               [0.863320, 0.283729, 0.412403],
+               [0.868793, 0.287728, 0.409303],
+               [0.874176, 0.291859, 0.406205],
+               [0.879464, 0.296125, 0.403118],
+               [0.884651, 0.300530, 0.400047],
+               [0.889731, 0.305079, 0.397002],
+               [0.894700, 0.309773, 0.393995],
+               [0.899552, 0.314616, 0.391037],
+               [0.904281, 0.319610, 0.388137],
+               [0.908884, 0.324755, 0.385308],
+               [0.913354, 0.330052, 0.382563],
+               [0.917689, 0.335500, 0.379915],
+               [0.921884, 0.341098, 0.377376],
+               [0.925937, 0.346844, 0.374959],
+               [0.929845, 0.352734, 0.372677],
+               [0.933606, 0.358764, 0.370541],
+               [0.937221, 0.364929, 0.368567],
+               [0.940687, 0.371224, 0.366762],
+               [0.944006, 0.377643, 0.365136],
+               [0.947180, 0.384178, 0.363701],
+               [0.950210, 0.390820, 0.362468],
+               [0.953099, 0.397563, 0.361438],
+               [0.955849, 0.404400, 0.360619],
+               [0.958464, 0.411324, 0.360014],
+               [0.960949, 0.418323, 0.359630],
+               [0.963310, 0.425390, 0.359469],
+               [0.965549, 0.432519, 0.359529],
+               [0.967671, 0.439703, 0.359810],
+               [0.969680, 0.446936, 0.360311],
+               [0.971582, 0.454210, 0.361030],
+               [0.973381, 0.461520, 0.361965],
+               [0.975082, 0.468861, 0.363111],
+               [0.976690, 0.476226, 0.364466],
+               [0.978210, 0.483612, 0.366025],
+               [0.979645, 0.491014, 0.367783],
+               [0.981000, 0.498428, 0.369734],
+               [0.982279, 0.505851, 0.371874],
+               [0.983485, 0.513280, 0.374198],
+               [0.984622, 0.520713, 0.376698],
+               [0.985693, 0.528148, 0.379371],
+               [0.986700, 0.535582, 0.382210],
+               [0.987646, 0.543015, 0.385210],
+               [0.988533, 0.550446, 0.388365],
+               [0.989363, 0.557873, 0.391671],
+               [0.990138, 0.565296, 0.395122],
+               [0.990871, 0.572706, 0.398714],
+               [0.991558, 0.580107, 0.402441],
+               [0.992196, 0.587502, 0.406299],
+               [0.992785, 0.594891, 0.410283],
+               [0.993326, 0.602275, 0.414390],
+               [0.993834, 0.609644, 0.418613],
+               [0.994309, 0.616999, 0.422950],
+               [0.994738, 0.624350, 0.427397],
+               [0.995122, 0.631696, 0.431951],
+               [0.995480, 0.639027, 0.436607],
+               [0.995810, 0.646344, 0.441361],
+               [0.996096, 0.653659, 0.446213],
+               [0.996341, 0.660969, 0.451160],
+               [0.996580, 0.668256, 0.456192],
+               [0.996775, 0.675541, 0.461314],
+               [0.996925, 0.682828, 0.466526],
+               [0.997077, 0.690088, 0.471811],
+               [0.997186, 0.697349, 0.477182],
+               [0.997254, 0.704611, 0.482635],
+               [0.997325, 0.711848, 0.488154],
+               [0.997351, 0.719089, 0.493755],
+               [0.997351, 0.726324, 0.499428],
+               [0.997341, 0.733545, 0.505167],
+               [0.997285, 0.740772, 0.510983],
+               [0.997228, 0.747981, 0.516859],
+               [0.997138, 0.755190, 0.522806],
+               [0.997019, 0.762398, 0.528821],
+               [0.996898, 0.769591, 0.534892],
+               [0.996727, 0.776795, 0.541039],
+               [0.996571, 0.783977, 0.547233],
+               [0.996369, 0.791167, 0.553499],
+               [0.996162, 0.798348, 0.559820],
+               [0.995932, 0.805527, 0.566202],
+               [0.995680, 0.812706, 0.572645],
+               [0.995424, 0.819875, 0.579140],
+               [0.995131, 0.827052, 0.585701],
+               [0.994851, 0.834213, 0.592307],
+               [0.994524, 0.841387, 0.598983],
+               [0.994222, 0.848540, 0.605696],
+               [0.993866, 0.855711, 0.612482],
+               [0.993545, 0.862859, 0.619299],
+               [0.993170, 0.870024, 0.626189],
+               [0.992831, 0.877168, 0.633109],
+               [0.992440, 0.884330, 0.640099],
+               [0.992089, 0.891470, 0.647116],
+               [0.991688, 0.898627, 0.654202],
+               [0.991332, 0.905763, 0.661309],
+               [0.990930, 0.912915, 0.668481],
+               [0.990570, 0.920049, 0.675675],
+               [0.990175, 0.927196, 0.682926],
+               [0.989815, 0.934329, 0.690198],
+               [0.989434, 0.941470, 0.697519],
+               [0.989077, 0.948604, 0.704863],
+               [0.988717, 0.955742, 0.712242],
+               [0.988367, 0.962878, 0.719649],
+               [0.988033, 0.970012, 0.727077],
+               [0.987691, 0.977154, 0.734536],
+               [0.987387, 0.984288, 0.742002],
+               [0.987053, 0.991438, 0.749504]]
+
+_inferno_data = [[0.001462, 0.000466, 0.013866],
+                 [0.002267, 0.001270, 0.018570],
+                 [0.003299, 0.002249, 0.024239],
+                 [0.004547, 0.003392, 0.030909],
+                 [0.006006, 0.004692, 0.038558],
+                 [0.007676, 0.006136, 0.046836],
+                 [0.009561, 0.007713, 0.055143],
+                 [0.011663, 0.009417, 0.063460],
+                 [0.013995, 0.011225, 0.071862],
+                 [0.016561, 0.013136, 0.080282],
+                 [0.019373, 0.015133, 0.088767],
+                 [0.022447, 0.017199, 0.097327],
+                 [0.025793, 0.019331, 0.105930],
+                 [0.029432, 0.021503, 0.114621],
+                 [0.033385, 0.023702, 0.123397],
+                 [0.037668, 0.025921, 0.132232],
+                 [0.042253, 0.028139, 0.141141],
+                 [0.046915, 0.030324, 0.150164],
+                 [0.051644, 0.032474, 0.159254],
+                 [0.056449, 0.034569, 0.168414],
+                 [0.061340, 0.036590, 0.177642],
+                 [0.066331, 0.038504, 0.186962],
+                 [0.071429, 0.040294, 0.196354],
+                 [0.076637, 0.041905, 0.205799],
+                 [0.081962, 0.043328, 0.215289],
+                 [0.087411, 0.044556, 0.224813],
+                 [0.092990, 0.045583, 0.234358],
+                 [0.098702, 0.046402, 0.243904],
+                 [0.104551, 0.047008, 0.253430],
+                 [0.110536, 0.047399, 0.262912],
+                 [0.116656, 0.047574, 0.272321],
+                 [0.122908, 0.047536, 0.281624],
+                 [0.129285, 0.047293, 0.290788],
+                 [0.135778, 0.046856, 0.299776],
+                 [0.142378, 0.046242, 0.308553],
+                 [0.149073, 0.045468, 0.317085],
+                 [0.155850, 0.044559, 0.325338],
+                 [0.162689, 0.043554, 0.333277],
+                 [0.169575, 0.042489, 0.340874],
+                 [0.176493, 0.041402, 0.348111],
+                 [0.183429, 0.040329, 0.354971],
+                 [0.190367, 0.039309, 0.361447],
+                 [0.197297, 0.038400, 0.367535],
+                 [0.204209, 0.037632, 0.373238],
+                 [0.211095, 0.037030, 0.378563],
+                 [0.217949, 0.036615, 0.383522],
+                 [0.224763, 0.036405, 0.388129],
+                 [0.231538, 0.036405, 0.392400],
+                 [0.238273, 0.036621, 0.396353],
+                 [0.244967, 0.037055, 0.400007],
+                 [0.251620, 0.037705, 0.403378],
+                 [0.258234, 0.038571, 0.406485],
+                 [0.264810, 0.039647, 0.409345],
+                 [0.271347, 0.040922, 0.411976],
+                 [0.277850, 0.042353, 0.414392],
+                 [0.284321, 0.043933, 0.416608],
+                 [0.290763, 0.045644, 0.418637],
+                 [0.297178, 0.047470, 0.420491],
+                 [0.303568, 0.049396, 0.422182],
+                 [0.309935, 0.051407, 0.423721],
+                 [0.316282, 0.053490, 0.425116],
+                 [0.322610, 0.055634, 0.426377],
+                 [0.328921, 0.057827, 0.427511],
+                 [0.335217, 0.060060, 0.428524],
+                 [0.341500, 0.062325, 0.429425],
+                 [0.347771, 0.064616, 0.430217],
+                 [0.354032, 0.066925, 0.430906],
+                 [0.360284, 0.069247, 0.431497],
+                 [0.366529, 0.071579, 0.431994],
+                 [0.372768, 0.073915, 0.432400],
+                 [0.379001, 0.076253, 0.432719],
+                 [0.385228, 0.078591, 0.432955],
+                 [0.391453, 0.080927, 0.433109],
+                 [0.397674, 0.083257, 0.433183],
+                 [0.403894, 0.085580, 0.433179],
+                 [0.410113, 0.087896, 0.433098],
+                 [0.416331, 0.090203, 0.432943],
+                 [0.422549, 0.092501, 0.432714],
+                 [0.428768, 0.094790, 0.432412],
+                 [0.434987, 0.097069, 0.432039],
+                 [0.441207, 0.099338, 0.431594],
+                 [0.447428, 0.101597, 0.431080],
+                 [0.453651, 0.103848, 0.430498],
+                 [0.459875, 0.106089, 0.429846],
+                 [0.466100, 0.108322, 0.429125],
+                 [0.472328, 0.110547, 0.428334],
+                 [0.478558, 0.112764, 0.427475],
+                 [0.484789, 0.114974, 0.426548],
+                 [0.491022, 0.117179, 0.425552],
+                 [0.497257, 0.119379, 0.424488],
+                 [0.503493, 0.121575, 0.423356],
+                 [0.509730, 0.123769, 0.422156],
+                 [0.515967, 0.125960, 0.420887],
+                 [0.522206, 0.128150, 0.419549],
+                 [0.528444, 0.130341, 0.418142],
+                 [0.534683, 0.132534, 0.416667],
+                 [0.540920, 0.134729, 0.415123],
+                 [0.547157, 0.136929, 0.413511],
+                 [0.553392, 0.139134, 0.411829],
+                 [0.559624, 0.141346, 0.410078],
+                 [0.565854, 0.143567, 0.408258],
+                 [0.572081, 0.145797, 0.406369],
+                 [0.578304, 0.148039, 0.404411],
+                 [0.584521, 0.150294, 0.402385],
+                 [0.590734, 0.152563, 0.400290],
+                 [0.596940, 0.154848, 0.398125],
+                 [0.603139, 0.157151, 0.395891],
+                 [0.609330, 0.159474, 0.393589],
+                 [0.615513, 0.161817, 0.391219],
+                 [0.621685, 0.164184, 0.388781],
+                 [0.627847, 0.166575, 0.386276],
+                 [0.633998, 0.168992, 0.383704],
+                 [0.640135, 0.171438, 0.381065],
+                 [0.646260, 0.173914, 0.378359],
+                 [0.652369, 0.176421, 0.375586],
+                 [0.658463, 0.178962, 0.372748],
+                 [0.664540, 0.181539, 0.369846],
+                 [0.670599, 0.184153, 0.366879],
+                 [0.676638, 0.186807, 0.363849],
+                 [0.682656, 0.189501, 0.360757],
+                 [0.688653, 0.192239, 0.357603],
+                 [0.694627, 0.195021, 0.354388],
+                 [0.700576, 0.197851, 0.351113],
+                 [0.706500, 0.200728, 0.347777],
+                 [0.712396, 0.203656, 0.344383],
+                 [0.718264, 0.206636, 0.340931],
+                 [0.724103, 0.209670, 0.337424],
+                 [0.729909, 0.212759, 0.333861],
+                 [0.735683, 0.215906, 0.330245],
+                 [0.741423, 0.219112, 0.326576],
+                 [0.747127, 0.222378, 0.322856],
+                 [0.752794, 0.225706, 0.319085],
+                 [0.758422, 0.229097, 0.315266],
+                 [0.764010, 0.232554, 0.311399],
+                 [0.769556, 0.236077, 0.307485],
+                 [0.775059, 0.239667, 0.303526],
+                 [0.780517, 0.243327, 0.299523],
+                 [0.785929, 0.247056, 0.295477],
+                 [0.791293, 0.250856, 0.291390],
+                 [0.796607, 0.254728, 0.287264],
+                 [0.801871, 0.258674, 0.283099],
+                 [0.807082, 0.262692, 0.278898],
+                 [0.812239, 0.266786, 0.274661],
+                 [0.817341, 0.270954, 0.270390],
+                 [0.822386, 0.275197, 0.266085],
+                 [0.827372, 0.279517, 0.261750],
+                 [0.832299, 0.283913, 0.257383],
+                 [0.837165, 0.288385, 0.252988],
+                 [0.841969, 0.292933, 0.248564],
+                 [0.846709, 0.297559, 0.244113],
+                 [0.851384, 0.302260, 0.239636],
+                 [0.855992, 0.307038, 0.235133],
+                 [0.860533, 0.311892, 0.230606],
+                 [0.865006, 0.316822, 0.226055],
+                 [0.869409, 0.321827, 0.221482],
+                 [0.873741, 0.326906, 0.216886],
+                 [0.878001, 0.332060, 0.212268],
+                 [0.882188, 0.337287, 0.207628],
+                 [0.886302, 0.342586, 0.202968],
+                 [0.890341, 0.347957, 0.198286],
+                 [0.894305, 0.353399, 0.193584],
+                 [0.898192, 0.358911, 0.188860],
+                 [0.902003, 0.364492, 0.184116],
+                 [0.905735, 0.370140, 0.179350],
+                 [0.909390, 0.375856, 0.174563],
+                 [0.912966, 0.381636, 0.169755],
+                 [0.916462, 0.387481, 0.164924],
+                 [0.919879, 0.393389, 0.160070],
+                 [0.923215, 0.399359, 0.155193],
+                 [0.926470, 0.405389, 0.150292],
+                 [0.929644, 0.411479, 0.145367],
+                 [0.932737, 0.417627, 0.140417],
+                 [0.935747, 0.423831, 0.135440],
+                 [0.938675, 0.430091, 0.130438],
+                 [0.941521, 0.436405, 0.125409],
+                 [0.944285, 0.442772, 0.120354],
+                 [0.946965, 0.449191, 0.115272],
+                 [0.949562, 0.455660, 0.110164],
+                 [0.952075, 0.462178, 0.105031],
+                 [0.954506, 0.468744, 0.099874],
+                 [0.956852, 0.475356, 0.094695],
+                 [0.959114, 0.482014, 0.089499],
+                 [0.961293, 0.488716, 0.084289],
+                 [0.963387, 0.495462, 0.079073],
+                 [0.965397, 0.502249, 0.073859],
+                 [0.967322, 0.509078, 0.068659],
+                 [0.969163, 0.515946, 0.063488],
+                 [0.970919, 0.522853, 0.058367],
+                 [0.972590, 0.529798, 0.053324],
+                 [0.974176, 0.536780, 0.048392],
+                 [0.975677, 0.543798, 0.043618],
+                 [0.977092, 0.550850, 0.039050],
+                 [0.978422, 0.557937, 0.034931],
+                 [0.979666, 0.565057, 0.031409],
+                 [0.980824, 0.572209, 0.028508],
+                 [0.981895, 0.579392, 0.026250],
+                 [0.982881, 0.586606, 0.024661],
+                 [0.983779, 0.593849, 0.023770],
+                 [0.984591, 0.601122, 0.023606],
+                 [0.985315, 0.608422, 0.024202],
+                 [0.985952, 0.615750, 0.025592],
+                 [0.986502, 0.623105, 0.027814],
+                 [0.986964, 0.630485, 0.030908],
+                 [0.987337, 0.637890, 0.034916],
+                 [0.987622, 0.645320, 0.039886],
+                 [0.987819, 0.652773, 0.045581],
+                 [0.987926, 0.660250, 0.051750],
+                 [0.987945, 0.667748, 0.058329],
+                 [0.987874, 0.675267, 0.065257],
+                 [0.987714, 0.682807, 0.072489],
+                 [0.987464, 0.690366, 0.079990],
+                 [0.987124, 0.697944, 0.087731],
+                 [0.986694, 0.705540, 0.095694],
+                 [0.986175, 0.713153, 0.103863],
+                 [0.985566, 0.720782, 0.112229],
+                 [0.984865, 0.728427, 0.120785],
+                 [0.984075, 0.736087, 0.129527],
+                 [0.983196, 0.743758, 0.138453],
+                 [0.982228, 0.751442, 0.147565],
+                 [0.981173, 0.759135, 0.156863],
+                 [0.980032, 0.766837, 0.166353],
+                 [0.978806, 0.774545, 0.176037],
+                 [0.977497, 0.782258, 0.185923],
+                 [0.976108, 0.789974, 0.196018],
+                 [0.974638, 0.797692, 0.206332],
+                 [0.973088, 0.805409, 0.216877],
+                 [0.971468, 0.813122, 0.227658],
+                 [0.969783, 0.820825, 0.238686],
+                 [0.968041, 0.828515, 0.249972],
+                 [0.966243, 0.836191, 0.261534],
+                 [0.964394, 0.843848, 0.273391],
+                 [0.962517, 0.851476, 0.285546],
+                 [0.960626, 0.859069, 0.298010],
+                 [0.958720, 0.866624, 0.310820],
+                 [0.956834, 0.874129, 0.323974],
+                 [0.954997, 0.881569, 0.337475],
+                 [0.953215, 0.888942, 0.351369],
+                 [0.951546, 0.896226, 0.365627],
+                 [0.950018, 0.903409, 0.380271],
+                 [0.948683, 0.910473, 0.395289],
+                 [0.947594, 0.917399, 0.410665],
+                 [0.946809, 0.924168, 0.426373],
+                 [0.946392, 0.930761, 0.442367],
+                 [0.946403, 0.937159, 0.458592],
+                 [0.946903, 0.943348, 0.474970],
+                 [0.947937, 0.949318, 0.491426],
+                 [0.949545, 0.955063, 0.507860],
+                 [0.951740, 0.960587, 0.524203],
+                 [0.954529, 0.965896, 0.540361],
+                 [0.957896, 0.971003, 0.556275],
+                 [0.961812, 0.975924, 0.571925],
+                 [0.966249, 0.980678, 0.587206],
+                 [0.971162, 0.985282, 0.602154],
+                 [0.976511, 0.989753, 0.616760],
+                 [0.982257, 0.994109, 0.631017],
+                 [0.988362, 0.998364, 0.644924]]
+
+_plasma_data = [[0.050383, 0.029803, 0.527975],
+                [0.063536, 0.028426, 0.533124],
+                [0.075353, 0.027206, 0.538007],
+                [0.086222, 0.026125, 0.542658],
+                [0.096379, 0.025165, 0.547103],
+                [0.105980, 0.024309, 0.551368],
+                [0.115124, 0.023556, 0.555468],
+                [0.123903, 0.022878, 0.559423],
+                [0.132381, 0.022258, 0.563250],
+                [0.140603, 0.021687, 0.566959],
+                [0.148607, 0.021154, 0.570562],
+                [0.156421, 0.020651, 0.574065],
+                [0.164070, 0.020171, 0.577478],
+                [0.171574, 0.019706, 0.580806],
+                [0.178950, 0.019252, 0.584054],
+                [0.186213, 0.018803, 0.587228],
+                [0.193374, 0.018354, 0.590330],
+                [0.200445, 0.017902, 0.593364],
+                [0.207435, 0.017442, 0.596333],
+                [0.214350, 0.016973, 0.599239],
+                [0.221197, 0.016497, 0.602083],
+                [0.227983, 0.016007, 0.604867],
+                [0.234715, 0.015502, 0.607592],
+                [0.241396, 0.014979, 0.610259],
+                [0.248032, 0.014439, 0.612868],
+                [0.254627, 0.013882, 0.615419],
+                [0.261183, 0.013308, 0.617911],
+                [0.267703, 0.012716, 0.620346],
+                [0.274191, 0.012109, 0.622722],
+                [0.280648, 0.011488, 0.625038],
+                [0.287076, 0.010855, 0.627295],
+                [0.293478, 0.010213, 0.629490],
+                [0.299855, 0.009561, 0.631624],
+                [0.306210, 0.008902, 0.633694],
+                [0.312543, 0.008239, 0.635700],
+                [0.318856, 0.007576, 0.637640],
+                [0.325150, 0.006915, 0.639512],
+                [0.331426, 0.006261, 0.641316],
+                [0.337683, 0.005618, 0.643049],
+                [0.343925, 0.004991, 0.644710],
+                [0.350150, 0.004382, 0.646298],
+                [0.356359, 0.003798, 0.647810],
+                [0.362553, 0.003243, 0.649245],
+                [0.368733, 0.002724, 0.650601],
+                [0.374897, 0.002245, 0.651876],
+                [0.381047, 0.001814, 0.653068],
+                [0.387183, 0.001434, 0.654177],
+                [0.393304, 0.001114, 0.655199],
+                [0.399411, 0.000859, 0.656133],
+                [0.405503, 0.000678, 0.656977],
+                [0.411580, 0.000577, 0.657730],
+                [0.417642, 0.000564, 0.658390],
+                [0.423689, 0.000646, 0.658956],
+                [0.429719, 0.000831, 0.659425],
+                [0.435734, 0.001127, 0.659797],
+                [0.441732, 0.001540, 0.660069],
+                [0.447714, 0.002080, 0.660240],
+                [0.453677, 0.002755, 0.660310],
+                [0.459623, 0.003574, 0.660277],
+                [0.465550, 0.004545, 0.660139],
+                [0.471457, 0.005678, 0.659897],
+                [0.477344, 0.006980, 0.659549],
+                [0.483210, 0.008460, 0.659095],
+                [0.489055, 0.010127, 0.658534],
+                [0.494877, 0.011990, 0.657865],
+                [0.500678, 0.014055, 0.657088],
+                [0.506454, 0.016333, 0.656202],
+                [0.512206, 0.018833, 0.655209],
+                [0.517933, 0.021563, 0.654109],
+                [0.523633, 0.024532, 0.652901],
+                [0.529306, 0.027747, 0.651586],
+                [0.534952, 0.031217, 0.650165],
+                [0.540570, 0.034950, 0.648640],
+                [0.546157, 0.038954, 0.647010],
+                [0.551715, 0.043136, 0.645277],
+                [0.557243, 0.047331, 0.643443],
+                [0.562738, 0.051545, 0.641509],
+                [0.568201, 0.055778, 0.639477],
+                [0.573632, 0.060028, 0.637349],
+                [0.579029, 0.064296, 0.635126],
+                [0.584391, 0.068579, 0.632812],
+                [0.589719, 0.072878, 0.630408],
+                [0.595011, 0.077190, 0.627917],
+                [0.600266, 0.081516, 0.625342],
+                [0.605485, 0.085854, 0.622686],
+                [0.610667, 0.090204, 0.619951],
+                [0.615812, 0.094564, 0.617140],
+                [0.620919, 0.098934, 0.614257],
+                [0.625987, 0.103312, 0.611305],
+                [0.631017, 0.107699, 0.608287],
+                [0.636008, 0.112092, 0.605205],
+                [0.640959, 0.116492, 0.602065],
+                [0.645872, 0.120898, 0.598867],
+                [0.650746, 0.125309, 0.595617],
+                [0.655580, 0.129725, 0.592317],
+                [0.660374, 0.134144, 0.588971],
+                [0.665129, 0.138566, 0.585582],
+                [0.669845, 0.142992, 0.582154],
+                [0.674522, 0.147419, 0.578688],
+                [0.679160, 0.151848, 0.575189],
+                [0.683758, 0.156278, 0.571660],
+                [0.688318, 0.160709, 0.568103],
+                [0.692840, 0.165141, 0.564522],
+                [0.697324, 0.169573, 0.560919],
+                [0.701769, 0.174005, 0.557296],
+                [0.706178, 0.178437, 0.553657],
+                [0.710549, 0.182868, 0.550004],
+                [0.714883, 0.187299, 0.546338],
+                [0.719181, 0.191729, 0.542663],
+                [0.723444, 0.196158, 0.538981],
+                [0.727670, 0.200586, 0.535293],
+                [0.731862, 0.205013, 0.531601],
+                [0.736019, 0.209439, 0.527908],
+                [0.740143, 0.213864, 0.524216],
+                [0.744232, 0.218288, 0.520524],
+                [0.748289, 0.222711, 0.516834],
+                [0.752312, 0.227133, 0.513149],
+                [0.756304, 0.231555, 0.509468],
+                [0.760264, 0.235976, 0.505794],
+                [0.764193, 0.240396, 0.502126],
+                [0.768090, 0.244817, 0.498465],
+                [0.771958, 0.249237, 0.494813],
+                [0.775796, 0.253658, 0.491171],
+                [0.779604, 0.258078, 0.487539],
+                [0.783383, 0.262500, 0.483918],
+                [0.787133, 0.266922, 0.480307],
+                [0.790855, 0.271345, 0.476706],
+                [0.794549, 0.275770, 0.473117],
+                [0.798216, 0.280197, 0.469538],
+                [0.801855, 0.284626, 0.465971],
+                [0.805467, 0.289057, 0.462415],
+                [0.809052, 0.293491, 0.458870],
+                [0.812612, 0.297928, 0.455338],
+                [0.816144, 0.302368, 0.451816],
+                [0.819651, 0.306812, 0.448306],
+                [0.823132, 0.311261, 0.444806],
+                [0.826588, 0.315714, 0.441316],
+                [0.830018, 0.320172, 0.437836],
+                [0.833422, 0.324635, 0.434366],
+                [0.836801, 0.329105, 0.430905],
+                [0.840155, 0.333580, 0.427455],
+                [0.843484, 0.338062, 0.424013],
+                [0.846788, 0.342551, 0.420579],
+                [0.850066, 0.347048, 0.417153],
+                [0.853319, 0.351553, 0.413734],
+                [0.856547, 0.356066, 0.410322],
+                [0.859750, 0.360588, 0.406917],
+                [0.862927, 0.365119, 0.403519],
+                [0.866078, 0.369660, 0.400126],
+                [0.869203, 0.374212, 0.396738],
+                [0.872303, 0.378774, 0.393355],
+                [0.875376, 0.383347, 0.389976],
+                [0.878423, 0.387932, 0.386600],
+                [0.881443, 0.392529, 0.383229],
+                [0.884436, 0.397139, 0.379860],
+                [0.887402, 0.401762, 0.376494],
+                [0.890340, 0.406398, 0.373130],
+                [0.893250, 0.411048, 0.369768],
+                [0.896131, 0.415712, 0.366407],
+                [0.898984, 0.420392, 0.363047],
+                [0.901807, 0.425087, 0.359688],
+                [0.904601, 0.429797, 0.356329],
+                [0.907365, 0.434524, 0.352970],
+                [0.910098, 0.439268, 0.349610],
+                [0.912800, 0.444029, 0.346251],
+                [0.915471, 0.448807, 0.342890],
+                [0.918109, 0.453603, 0.339529],
+                [0.920714, 0.458417, 0.336166],
+                [0.923287, 0.463251, 0.332801],
+                [0.925825, 0.468103, 0.329435],
+                [0.928329, 0.472975, 0.326067],
+                [0.930798, 0.477867, 0.322697],
+                [0.933232, 0.482780, 0.319325],
+                [0.935630, 0.487712, 0.315952],
+                [0.937990, 0.492667, 0.312575],
+                [0.940313, 0.497642, 0.309197],
+                [0.942598, 0.502639, 0.305816],
+                [0.944844, 0.507658, 0.302433],
+                [0.947051, 0.512699, 0.299049],
+                [0.949217, 0.517763, 0.295662],
+                [0.951344, 0.522850, 0.292275],
+                [0.953428, 0.527960, 0.288883],
+                [0.955470, 0.533093, 0.285490],
+                [0.957469, 0.538250, 0.282096],
+                [0.959424, 0.543431, 0.278701],
+                [0.961336, 0.548636, 0.275305],
+                [0.963203, 0.553865, 0.271909],
+                [0.965024, 0.559118, 0.268513],
+                [0.966798, 0.564396, 0.265118],
+                [0.968526, 0.569700, 0.261721],
+                [0.970205, 0.575028, 0.258325],
+                [0.971835, 0.580382, 0.254931],
+                [0.973416, 0.585761, 0.251540],
+                [0.974947, 0.591165, 0.248151],
+                [0.976428, 0.596595, 0.244767],
+                [0.977856, 0.602051, 0.241387],
+                [0.979233, 0.607532, 0.238013],
+                [0.980556, 0.613039, 0.234646],
+                [0.981826, 0.618572, 0.231287],
+                [0.983041, 0.624131, 0.227937],
+                [0.984199, 0.629718, 0.224595],
+                [0.985301, 0.635330, 0.221265],
+                [0.986345, 0.640969, 0.217948],
+                [0.987332, 0.646633, 0.214648],
+                [0.988260, 0.652325, 0.211364],
+                [0.989128, 0.658043, 0.208100],
+                [0.989935, 0.663787, 0.204859],
+                [0.990681, 0.669558, 0.201642],
+                [0.991365, 0.675355, 0.198453],
+                [0.991985, 0.681179, 0.195295],
+                [0.992541, 0.687030, 0.192170],
+                [0.993032, 0.692907, 0.189084],
+                [0.993456, 0.698810, 0.186041],
+                [0.993814, 0.704741, 0.183043],
+                [0.994103, 0.710698, 0.180097],
+                [0.994324, 0.716681, 0.177208],
+                [0.994474, 0.722691, 0.174381],
+                [0.994553, 0.728728, 0.171622],
+                [0.994561, 0.734791, 0.168938],
+                [0.994495, 0.740880, 0.166335],
+                [0.994355, 0.746995, 0.163821],
+                [0.994141, 0.753137, 0.161404],
+                [0.993851, 0.759304, 0.159092],
+                [0.993482, 0.765499, 0.156891],
+                [0.993033, 0.771720, 0.154808],
+                [0.992505, 0.777967, 0.152855],
+                [0.991897, 0.784239, 0.151042],
+                [0.991209, 0.790537, 0.149377],
+                [0.990439, 0.796859, 0.147870],
+                [0.989587, 0.803205, 0.146529],
+                [0.988648, 0.809579, 0.145357],
+                [0.987621, 0.815978, 0.144363],
+                [0.986509, 0.822401, 0.143557],
+                [0.985314, 0.828846, 0.142945],
+                [0.984031, 0.835315, 0.142528],
+                [0.982653, 0.841812, 0.142303],
+                [0.981190, 0.848329, 0.142279],
+                [0.979644, 0.854866, 0.142453],
+                [0.977995, 0.861432, 0.142808],
+                [0.976265, 0.868016, 0.143351],
+                [0.974443, 0.874622, 0.144061],
+                [0.972530, 0.881250, 0.144923],
+                [0.970533, 0.887896, 0.145919],
+                [0.968443, 0.894564, 0.147014],
+                [0.966271, 0.901249, 0.148180],
+                [0.964021, 0.907950, 0.149370],
+                [0.961681, 0.914672, 0.150520],
+                [0.959276, 0.921407, 0.151566],
+                [0.956808, 0.928152, 0.152409],
+                [0.954287, 0.934908, 0.152921],
+                [0.951726, 0.941671, 0.152925],
+                [0.949151, 0.948435, 0.152178],
+                [0.946602, 0.955190, 0.150328],
+                [0.944152, 0.961916, 0.146861],
+                [0.941896, 0.968590, 0.140956],
+                [0.940015, 0.975158, 0.131326]]
+
+_viridis_data = [[0.267004, 0.004874, 0.329415],
+                 [0.268510, 0.009605, 0.335427],
+                 [0.269944, 0.014625, 0.341379],
+                 [0.271305, 0.019942, 0.347269],
+                 [0.272594, 0.025563, 0.353093],
+                 [0.273809, 0.031497, 0.358853],
+                 [0.274952, 0.037752, 0.364543],
+                 [0.276022, 0.044167, 0.370164],
+                 [0.277018, 0.050344, 0.375715],
+                 [0.277941, 0.056324, 0.381191],
+                 [0.278791, 0.062145, 0.386592],
+                 [0.279566, 0.067836, 0.391917],
+                 [0.280267, 0.073417, 0.397163],
+                 [0.280894, 0.078907, 0.402329],
+                 [0.281446, 0.084320, 0.407414],
+                 [0.281924, 0.089666, 0.412415],
+                 [0.282327, 0.094955, 0.417331],
+                 [0.282656, 0.100196, 0.422160],
+                 [0.282910, 0.105393, 0.426902],
+                 [0.283091, 0.110553, 0.431554],
+                 [0.283197, 0.115680, 0.436115],
+                 [0.283229, 0.120777, 0.440584],
+                 [0.283187, 0.125848, 0.444960],
+                 [0.283072, 0.130895, 0.449241],
+                 [0.282884, 0.135920, 0.453427],
+                 [0.282623, 0.140926, 0.457517],
+                 [0.282290, 0.145912, 0.461510],
+                 [0.281887, 0.150881, 0.465405],
+                 [0.281412, 0.155834, 0.469201],
+                 [0.280868, 0.160771, 0.472899],
+                 [0.280255, 0.165693, 0.476498],
+                 [0.279574, 0.170599, 0.479997],
+                 [0.278826, 0.175490, 0.483397],
+                 [0.278012, 0.180367, 0.486697],
+                 [0.277134, 0.185228, 0.489898],
+                 [0.276194, 0.190074, 0.493001],
+                 [0.275191, 0.194905, 0.496005],
+                 [0.274128, 0.199721, 0.498911],
+                 [0.273006, 0.204520, 0.501721],
+                 [0.271828, 0.209303, 0.504434],
+                 [0.270595, 0.214069, 0.507052],
+                 [0.269308, 0.218818, 0.509577],
+                 [0.267968, 0.223549, 0.512008],
+                 [0.266580, 0.228262, 0.514349],
+                 [0.265145, 0.232956, 0.516599],
+                 [0.263663, 0.237631, 0.518762],
+                 [0.262138, 0.242286, 0.520837],
+                 [0.260571, 0.246922, 0.522828],
+                 [0.258965, 0.251537, 0.524736],
+                 [0.257322, 0.256130, 0.526563],
+                 [0.255645, 0.260703, 0.528312],
+                 [0.253935, 0.265254, 0.529983],
+                 [0.252194, 0.269783, 0.531579],
+                 [0.250425, 0.274290, 0.533103],
+                 [0.248629, 0.278775, 0.534556],
+                 [0.246811, 0.283237, 0.535941],
+                 [0.244972, 0.287675, 0.537260],
+                 [0.243113, 0.292092, 0.538516],
+                 [0.241237, 0.296485, 0.539709],
+                 [0.239346, 0.300855, 0.540844],
+                 [0.237441, 0.305202, 0.541921],
+                 [0.235526, 0.309527, 0.542944],
+                 [0.233603, 0.313828, 0.543914],
+                 [0.231674, 0.318106, 0.544834],
+                 [0.229739, 0.322361, 0.545706],
+                 [0.227802, 0.326594, 0.546532],
+                 [0.225863, 0.330805, 0.547314],
+                 [0.223925, 0.334994, 0.548053],
+                 [0.221989, 0.339161, 0.548752],
+                 [0.220057, 0.343307, 0.549413],
+                 [0.218130, 0.347432, 0.550038],
+                 [0.216210, 0.351535, 0.550627],
+                 [0.214298, 0.355619, 0.551184],
+                 [0.212395, 0.359683, 0.551710],
+                 [0.210503, 0.363727, 0.552206],
+                 [0.208623, 0.367752, 0.552675],
+                 [0.206756, 0.371758, 0.553117],
+                 [0.204903, 0.375746, 0.553533],
+                 [0.203063, 0.379716, 0.553925],
+                 [0.201239, 0.383670, 0.554294],
+                 [0.199430, 0.387607, 0.554642],
+                 [0.197636, 0.391528, 0.554969],
+                 [0.195860, 0.395433, 0.555276],
+                 [0.194100, 0.399323, 0.555565],
+                 [0.192357, 0.403199, 0.555836],
+                 [0.190631, 0.407061, 0.556089],
+                 [0.188923, 0.410910, 0.556326],
+                 [0.187231, 0.414746, 0.556547],
+                 [0.185556, 0.418570, 0.556753],
+                 [0.183898, 0.422383, 0.556944],
+                 [0.182256, 0.426184, 0.557120],
+                 [0.180629, 0.429975, 0.557282],
+                 [0.179019, 0.433756, 0.557430],
+                 [0.177423, 0.437527, 0.557565],
+                 [0.175841, 0.441290, 0.557685],
+                 [0.174274, 0.445044, 0.557792],
+                 [0.172719, 0.448791, 0.557885],
+                 [0.171176, 0.452530, 0.557965],
+                 [0.169646, 0.456262, 0.558030],
+                 [0.168126, 0.459988, 0.558082],
+                 [0.166617, 0.463708, 0.558119],
+                 [0.165117, 0.467423, 0.558141],
+                 [0.163625, 0.471133, 0.558148],
+                 [0.162142, 0.474838, 0.558140],
+                 [0.160665, 0.478540, 0.558115],
+                 [0.159194, 0.482237, 0.558073],
+                 [0.157729, 0.485932, 0.558013],
+                 [0.156270, 0.489624, 0.557936],
+                 [0.154815, 0.493313, 0.557840],
+                 [0.153364, 0.497000, 0.557724],
+                 [0.151918, 0.500685, 0.557587],
+                 [0.150476, 0.504369, 0.557430],
+                 [0.149039, 0.508051, 0.557250],
+                 [0.147607, 0.511733, 0.557049],
+                 [0.146180, 0.515413, 0.556823],
+                 [0.144759, 0.519093, 0.556572],
+                 [0.143343, 0.522773, 0.556295],
+                 [0.141935, 0.526453, 0.555991],
+                 [0.140536, 0.530132, 0.555659],
+                 [0.139147, 0.533812, 0.555298],
+                 [0.137770, 0.537492, 0.554906],
+                 [0.136408, 0.541173, 0.554483],
+                 [0.135066, 0.544853, 0.554029],
+                 [0.133743, 0.548535, 0.553541],
+                 [0.132444, 0.552216, 0.553018],
+                 [0.131172, 0.555899, 0.552459],
+                 [0.129933, 0.559582, 0.551864],
+                 [0.128729, 0.563265, 0.551229],
+                 [0.127568, 0.566949, 0.550556],
+                 [0.126453, 0.570633, 0.549841],
+                 [0.125394, 0.574318, 0.549086],
+                 [0.124395, 0.578002, 0.548287],
+                 [0.123463, 0.581687, 0.547445],
+                 [0.122606, 0.585371, 0.546557],
+                 [0.121831, 0.589055, 0.545623],
+                 [0.121148, 0.592739, 0.544641],
+                 [0.120565, 0.596422, 0.543611],
+                 [0.120092, 0.600104, 0.542530],
+                 [0.119738, 0.603785, 0.541400],
+                 [0.119512, 0.607464, 0.540218],
+                 [0.119423, 0.611141, 0.538982],
+                 [0.119483, 0.614817, 0.537692],
+                 [0.119699, 0.618490, 0.536347],
+                 [0.120081, 0.622161, 0.534946],
+                 [0.120638, 0.625828, 0.533488],
+                 [0.121380, 0.629492, 0.531973],
+                 [0.122312, 0.633153, 0.530398],
+                 [0.123444, 0.636809, 0.528763],
+                 [0.124780, 0.640461, 0.527068],
+                 [0.126326, 0.644107, 0.525311],
+                 [0.128087, 0.647749, 0.523491],
+                 [0.130067, 0.651384, 0.521608],
+                 [0.132268, 0.655014, 0.519661],
+                 [0.134692, 0.658636, 0.517649],
+                 [0.137339, 0.662252, 0.515571],
+                 [0.140210, 0.665859, 0.513427],
+                 [0.143303, 0.669459, 0.511215],
+                 [0.146616, 0.673050, 0.508936],
+                 [0.150148, 0.676631, 0.506589],
+                 [0.153894, 0.680203, 0.504172],
+                 [0.157851, 0.683765, 0.501686],
+                 [0.162016, 0.687316, 0.499129],
+                 [0.166383, 0.690856, 0.496502],
+                 [0.170948, 0.694384, 0.493803],
+                 [0.175707, 0.697900, 0.491033],
+                 [0.180653, 0.701402, 0.488189],
+                 [0.185783, 0.704891, 0.485273],
+                 [0.191090, 0.708366, 0.482284],
+                 [0.196571, 0.711827, 0.479221],
+                 [0.202219, 0.715272, 0.476084],
+                 [0.208030, 0.718701, 0.472873],
+                 [0.214000, 0.722114, 0.469588],
+                 [0.220124, 0.725509, 0.466226],
+                 [0.226397, 0.728888, 0.462789],
+                 [0.232815, 0.732247, 0.459277],
+                 [0.239374, 0.735588, 0.455688],
+                 [0.246070, 0.738910, 0.452024],
+                 [0.252899, 0.742211, 0.448284],
+                 [0.259857, 0.745492, 0.444467],
+                 [0.266941, 0.748751, 0.440573],
+                 [0.274149, 0.751988, 0.436601],
+                 [0.281477, 0.755203, 0.432552],
+                 [0.288921, 0.758394, 0.428426],
+                 [0.296479, 0.761561, 0.424223],
+                 [0.304148, 0.764704, 0.419943],
+                 [0.311925, 0.767822, 0.415586],
+                 [0.319809, 0.770914, 0.411152],
+                 [0.327796, 0.773980, 0.406640],
+                 [0.335885, 0.777018, 0.402049],
+                 [0.344074, 0.780029, 0.397381],
+                 [0.352360, 0.783011, 0.392636],
+                 [0.360741, 0.785964, 0.387814],
+                 [0.369214, 0.788888, 0.382914],
+                 [0.377779, 0.791781, 0.377939],
+                 [0.386433, 0.794644, 0.372886],
+                 [0.395174, 0.797475, 0.367757],
+                 [0.404001, 0.800275, 0.362552],
+                 [0.412913, 0.803041, 0.357269],
+                 [0.421908, 0.805774, 0.351910],
+                 [0.430983, 0.808473, 0.346476],
+                 [0.440137, 0.811138, 0.340967],
+                 [0.449368, 0.813768, 0.335384],
+                 [0.458674, 0.816363, 0.329727],
+                 [0.468053, 0.818921, 0.323998],
+                 [0.477504, 0.821444, 0.318195],
+                 [0.487026, 0.823929, 0.312321],
+                 [0.496615, 0.826376, 0.306377],
+                 [0.506271, 0.828786, 0.300362],
+                 [0.515992, 0.831158, 0.294279],
+                 [0.525776, 0.833491, 0.288127],
+                 [0.535621, 0.835785, 0.281908],
+                 [0.545524, 0.838039, 0.275626],
+                 [0.555484, 0.840254, 0.269281],
+                 [0.565498, 0.842430, 0.262877],
+                 [0.575563, 0.844566, 0.256415],
+                 [0.585678, 0.846661, 0.249897],
+                 [0.595839, 0.848717, 0.243329],
+                 [0.606045, 0.850733, 0.236712],
+                 [0.616293, 0.852709, 0.230052],
+                 [0.626579, 0.854645, 0.223353],
+                 [0.636902, 0.856542, 0.216620],
+                 [0.647257, 0.858400, 0.209861],
+                 [0.657642, 0.860219, 0.203082],
+                 [0.668054, 0.861999, 0.196293],
+                 [0.678489, 0.863742, 0.189503],
+                 [0.688944, 0.865448, 0.182725],
+                 [0.699415, 0.867117, 0.175971],
+                 [0.709898, 0.868751, 0.169257],
+                 [0.720391, 0.870350, 0.162603],
+                 [0.730889, 0.871916, 0.156029],
+                 [0.741388, 0.873449, 0.149561],
+                 [0.751884, 0.874951, 0.143228],
+                 [0.762373, 0.876424, 0.137064],
+                 [0.772852, 0.877868, 0.131109],
+                 [0.783315, 0.879285, 0.125405],
+                 [0.793760, 0.880678, 0.120005],
+                 [0.804182, 0.882046, 0.114965],
+                 [0.814576, 0.883393, 0.110347],
+                 [0.824940, 0.884720, 0.106217],
+                 [0.835270, 0.886029, 0.102646],
+                 [0.845561, 0.887322, 0.099702],
+                 [0.855810, 0.888601, 0.097452],
+                 [0.866013, 0.889868, 0.095953],
+                 [0.876168, 0.891125, 0.095250],
+                 [0.886271, 0.892374, 0.095374],
+                 [0.896320, 0.893616, 0.096335],
+                 [0.906311, 0.894855, 0.098125],
+                 [0.916242, 0.896091, 0.100717],
+                 [0.926106, 0.897330, 0.104071],
+                 [0.935904, 0.898570, 0.108131],
+                 [0.945636, 0.899815, 0.112838],
+                 [0.955300, 0.901065, 0.118128],
+                 [0.964894, 0.902323, 0.123941],
+                 [0.974417, 0.903590, 0.130215],
+                 [0.983868, 0.904867, 0.136897],
+                 [0.993248, 0.906157, 0.143936]]
+
+_cividis_data = [[0.000000, 0.135112, 0.304751],
+                 [0.000000, 0.138068, 0.311105],
+                 [0.000000, 0.141013, 0.317579],
+                 [0.000000, 0.143951, 0.323982],
+                 [0.000000, 0.146877, 0.330479],
+                 [0.000000, 0.149791, 0.337065],
+                 [0.000000, 0.152673, 0.343704],
+                 [0.000000, 0.155377, 0.350500],
+                 [0.000000, 0.157932, 0.357521],
+                 [0.000000, 0.160495, 0.364534],
+                 [0.000000, 0.163058, 0.371608],
+                 [0.000000, 0.165621, 0.378769],
+                 [0.000000, 0.168204, 0.385902],
+                 [0.000000, 0.170800, 0.393100],
+                 [0.000000, 0.173420, 0.400353],
+                 [0.000000, 0.176082, 0.407577],
+                 [0.000000, 0.178802, 0.414764],
+                 [0.000000, 0.181610, 0.421859],
+                 [0.000000, 0.184550, 0.428802],
+                 [0.000000, 0.186915, 0.435532],
+                 [0.000000, 0.188769, 0.439563],
+                 [0.000000, 0.190950, 0.441085],
+                 [0.000000, 0.193366, 0.441561],
+                 [0.003602, 0.195911, 0.441564],
+                 [0.017852, 0.198528, 0.441248],
+                 [0.032110, 0.201199, 0.440785],
+                 [0.046205, 0.203903, 0.440196],
+                 [0.058378, 0.206629, 0.439531],
+                 [0.068968, 0.209372, 0.438863],
+                 [0.078624, 0.212122, 0.438105],
+                 [0.087465, 0.214879, 0.437342],
+                 [0.095645, 0.217643, 0.436593],
+                 [0.103401, 0.220406, 0.435790],
+                 [0.110658, 0.223170, 0.435067],
+                 [0.117612, 0.225935, 0.434308],
+                 [0.124291, 0.228697, 0.433547],
+                 [0.130669, 0.231458, 0.432840],
+                 [0.136830, 0.234216, 0.432148],
+                 [0.142852, 0.236972, 0.431404],
+                 [0.148638, 0.239724, 0.430752],
+                 [0.154261, 0.242475, 0.430120],
+                 [0.159733, 0.245221, 0.429528],
+                 [0.165113, 0.247965, 0.428908],
+                 [0.170362, 0.250707, 0.428325],
+                 [0.175490, 0.253444, 0.427790],
+                 [0.180503, 0.256180, 0.427299],
+                 [0.185453, 0.258914, 0.426788],
+                 [0.190303, 0.261644, 0.426329],
+                 [0.195057, 0.264372, 0.425924],
+                 [0.199764, 0.267099, 0.425497],
+                 [0.204385, 0.269823, 0.425126],
+                 [0.208926, 0.272546, 0.424809],
+                 [0.213431, 0.275266, 0.424480],
+                 [0.217863, 0.277985, 0.424206],
+                 [0.222264, 0.280702, 0.423914],
+                 [0.226598, 0.283419, 0.423678],
+                 [0.230871, 0.286134, 0.423498],
+                 [0.235120, 0.288848, 0.423304],
+                 [0.239312, 0.291562, 0.423167],
+                 [0.243485, 0.294274, 0.423014],
+                 [0.247605, 0.296986, 0.422917],
+                 [0.251675, 0.299698, 0.422873],
+                 [0.255731, 0.302409, 0.422814],
+                 [0.259740, 0.305120, 0.422810],
+                 [0.263738, 0.307831, 0.422789],
+                 [0.267693, 0.310542, 0.422821],
+                 [0.271639, 0.313253, 0.422837],
+                 [0.275513, 0.315965, 0.422979],
+                 [0.279411, 0.318677, 0.423031],
+                 [0.283240, 0.321390, 0.423211],
+                 [0.287065, 0.324103, 0.423373],
+                 [0.290884, 0.326816, 0.423517],
+                 [0.294669, 0.329531, 0.423716],
+                 [0.298421, 0.332247, 0.423973],
+                 [0.302169, 0.334963, 0.424213],
+                 [0.305886, 0.337681, 0.424512],
+                 [0.309601, 0.340399, 0.424790],
+                 [0.313287, 0.343120, 0.425120],
+                 [0.316941, 0.345842, 0.425512],
+                 [0.320595, 0.348565, 0.425889],
+                 [0.324250, 0.351289, 0.426250],
+                 [0.327875, 0.354016, 0.426670],
+                 [0.331474, 0.356744, 0.427144],
+                 [0.335073, 0.359474, 0.427605],
+                 [0.338673, 0.362206, 0.428053],
+                 [0.342246, 0.364939, 0.428559],
+                 [0.345793, 0.367676, 0.429127],
+                 [0.349341, 0.370414, 0.429685],
+                 [0.352892, 0.373153, 0.430226],
+                 [0.356418, 0.375896, 0.430823],
+                 [0.359916, 0.378641, 0.431501],
+                 [0.363446, 0.381388, 0.432075],
+                 [0.366923, 0.384139, 0.432796],
+                 [0.370430, 0.386890, 0.433428],
+                 [0.373884, 0.389646, 0.434209],
+                 [0.377371, 0.392404, 0.434890],
+                 [0.380830, 0.395164, 0.435653],
+                 [0.384268, 0.397928, 0.436475],
+                 [0.387705, 0.400694, 0.437305],
+                 [0.391151, 0.403464, 0.438096],
+                 [0.394568, 0.406236, 0.438986],
+                 [0.397991, 0.409011, 0.439848],
+                 [0.401418, 0.411790, 0.440708],
+                 [0.404820, 0.414572, 0.441642],
+                 [0.408226, 0.417357, 0.442570],
+                 [0.411607, 0.420145, 0.443577],
+                 [0.414992, 0.422937, 0.444578],
+                 [0.418383, 0.425733, 0.445560],
+                 [0.421748, 0.428531, 0.446640],
+                 [0.425120, 0.431334, 0.447692],
+                 [0.428462, 0.434140, 0.448864],
+                 [0.431817, 0.436950, 0.449982],
+                 [0.435168, 0.439763, 0.451134],
+                 [0.438504, 0.442580, 0.452341],
+                 [0.441810, 0.445402, 0.453659],
+                 [0.445148, 0.448226, 0.454885],
+                 [0.448447, 0.451053, 0.456264],
+                 [0.451759, 0.453887, 0.457582],
+                 [0.455072, 0.456718, 0.458976],
+                 [0.458366, 0.459552, 0.460457],
+                 [0.461616, 0.462405, 0.461969],
+                 [0.464947, 0.465241, 0.463395],
+                 [0.468254, 0.468083, 0.464908],
+                 [0.471501, 0.470960, 0.466357],
+                 [0.474812, 0.473832, 0.467681],
+                 [0.478186, 0.476699, 0.468845],
+                 [0.481622, 0.479573, 0.469767],
+                 [0.485141, 0.482451, 0.470384],
+                 [0.488697, 0.485318, 0.471008],
+                 [0.492278, 0.488198, 0.471453],
+                 [0.495913, 0.491076, 0.471751],
+                 [0.499552, 0.493960, 0.472032],
+                 [0.503185, 0.496851, 0.472305],
+                 [0.506866, 0.499743, 0.472432],
+                 [0.510540, 0.502643, 0.472550],
+                 [0.514226, 0.505546, 0.472640],
+                 [0.517920, 0.508454, 0.472707],
+                 [0.521643, 0.511367, 0.472639],
+                 [0.525348, 0.514285, 0.472660],
+                 [0.529086, 0.517207, 0.472543],
+                 [0.532829, 0.520135, 0.472401],
+                 [0.536553, 0.523067, 0.472352],
+                 [0.540307, 0.526005, 0.472163],
+                 [0.544069, 0.528948, 0.471947],
+                 [0.547840, 0.531895, 0.471704],
+                 [0.551612, 0.534849, 0.471439],
+                 [0.555393, 0.537807, 0.471147],
+                 [0.559181, 0.540771, 0.470829],
+                 [0.562972, 0.543741, 0.470488],
+                 [0.566802, 0.546715, 0.469988],
+                 [0.570607, 0.549695, 0.469593],
+                 [0.574417, 0.552682, 0.469172],
+                 [0.578236, 0.555673, 0.468724],
+                 [0.582087, 0.558670, 0.468118],
+                 [0.585916, 0.561674, 0.467618],
+                 [0.589753, 0.564682, 0.467090],
+                 [0.593622, 0.567697, 0.466401],
+                 [0.597469, 0.570718, 0.465821],
+                 [0.601354, 0.573743, 0.465074],
+                 [0.605211, 0.576777, 0.464441],
+                 [0.609105, 0.579816, 0.463638],
+                 [0.612977, 0.582861, 0.462950],
+                 [0.616852, 0.585913, 0.462237],
+                 [0.620765, 0.588970, 0.461351],
+                 [0.624654, 0.592034, 0.460583],
+                 [0.628576, 0.595104, 0.459641],
+                 [0.632506, 0.598180, 0.458668],
+                 [0.636412, 0.601264, 0.457818],
+                 [0.640352, 0.604354, 0.456791],
+                 [0.644270, 0.607450, 0.455886],
+                 [0.648222, 0.610553, 0.454801],
+                 [0.652178, 0.613664, 0.453689],
+                 [0.656114, 0.616780, 0.452702],
+                 [0.660082, 0.619904, 0.451534],
+                 [0.664055, 0.623034, 0.450338],
+                 [0.668008, 0.626171, 0.449270],
+                 [0.671991, 0.629316, 0.448018],
+                 [0.675981, 0.632468, 0.446736],
+                 [0.679979, 0.635626, 0.445424],
+                 [0.683950, 0.638793, 0.444251],
+                 [0.687957, 0.641966, 0.442886],
+                 [0.691971, 0.645145, 0.441491],
+                 [0.695985, 0.648334, 0.440072],
+                 [0.700008, 0.651529, 0.438624],
+                 [0.704037, 0.654731, 0.437147],
+                 [0.708067, 0.657942, 0.435647],
+                 [0.712105, 0.661160, 0.434117],
+                 [0.716177, 0.664384, 0.432386],
+                 [0.720222, 0.667618, 0.430805],
+                 [0.724274, 0.670859, 0.429194],
+                 [0.728334, 0.674107, 0.427554],
+                 [0.732422, 0.677364, 0.425717],
+                 [0.736488, 0.680629, 0.424028],
+                 [0.740589, 0.683900, 0.422131],
+                 [0.744664, 0.687181, 0.420393],
+                 [0.748772, 0.690470, 0.418448],
+                 [0.752886, 0.693766, 0.416472],
+                 [0.756975, 0.697071, 0.414659],
+                 [0.761096, 0.700384, 0.412638],
+                 [0.765223, 0.703705, 0.410587],
+                 [0.769353, 0.707035, 0.408516],
+                 [0.773486, 0.710373, 0.406422],
+                 [0.777651, 0.713719, 0.404112],
+                 [0.781795, 0.717074, 0.401966],
+                 [0.785965, 0.720438, 0.399613],
+                 [0.790116, 0.723810, 0.397423],
+                 [0.794298, 0.727190, 0.395016],
+                 [0.798480, 0.730580, 0.392597],
+                 [0.802667, 0.733978, 0.390153],
+                 [0.806859, 0.737385, 0.387684],
+                 [0.811054, 0.740801, 0.385198],
+                 [0.815274, 0.744226, 0.382504],
+                 [0.819499, 0.747659, 0.379785],
+                 [0.823729, 0.751101, 0.377043],
+                 [0.827959, 0.754553, 0.374292],
+                 [0.832192, 0.758014, 0.371529],
+                 [0.836429, 0.761483, 0.368747],
+                 [0.840693, 0.764962, 0.365746],
+                 [0.844957, 0.768450, 0.362741],
+                 [0.849223, 0.771947, 0.359729],
+                 [0.853515, 0.775454, 0.356500],
+                 [0.857809, 0.778969, 0.353259],
+                 [0.862105, 0.782494, 0.350011],
+                 [0.866421, 0.786028, 0.346571],
+                 [0.870717, 0.789572, 0.343333],
+                 [0.875057, 0.793125, 0.339685],
+                 [0.879378, 0.796687, 0.336241],
+                 [0.883720, 0.800258, 0.332599],
+                 [0.888081, 0.803839, 0.328770],
+                 [0.892440, 0.807430, 0.324968],
+                 [0.896818, 0.811030, 0.320982],
+                 [0.901195, 0.814639, 0.317021],
+                 [0.905589, 0.818257, 0.312889],
+                 [0.910000, 0.821885, 0.308594],
+                 [0.914407, 0.825522, 0.304348],
+                 [0.918828, 0.829168, 0.299960],
+                 [0.923279, 0.832822, 0.295244],
+                 [0.927724, 0.836486, 0.290611],
+                 [0.932180, 0.840159, 0.285880],
+                 [0.936660, 0.843841, 0.280876],
+                 [0.941147, 0.847530, 0.275815],
+                 [0.945654, 0.851228, 0.270532],
+                 [0.950178, 0.854933, 0.265085],
+                 [0.954725, 0.858646, 0.259365],
+                 [0.959284, 0.862365, 0.253563],
+                 [0.963872, 0.866089, 0.247445],
+                 [0.968469, 0.869819, 0.241310],
+                 [0.973114, 0.873550, 0.234677],
+                 [0.977780, 0.877281, 0.227954],
+                 [0.982497, 0.881008, 0.220878],
+                 [0.987293, 0.884718, 0.213336],
+                 [0.992218, 0.888385, 0.205468],
+                 [0.994847, 0.892954, 0.203445],
+                 [0.995249, 0.898384, 0.207561],
+                 [0.995503, 0.903866, 0.212370],
+                 [0.995737, 0.909344, 0.217772]]
+
+_twilight_data = [
+    [0.88575015840754434, 0.85000924943067835,  0.8879736506427196],
+    [0.88378520195539056, 0.85072940540310626,  0.88723222096949894],
+    [0.88172231059285788, 0.85127594077653468,  0.88638056925514819],
+    [0.8795410528270573,  0.85165675407495722,  0.8854143767924102],
+    [0.87724880858965482, 0.85187028338870274,  0.88434120381311432],
+    [0.87485347508575972, 0.85191526123023187,  0.88316926967613829],
+    [0.87233134085124076, 0.85180165478080894,  0.88189704355001619],
+    [0.86970474853509816, 0.85152403004797894,  0.88053883390003362],
+    [0.86696015505333579, 0.8510896085314068,   0.87909766977173343],
+    [0.86408985081463996, 0.85050391167507788,  0.87757925784892632],
+    [0.86110245436899846, 0.84976754857001258,  0.87599242923439569],
+    [0.85798259245670372, 0.84888934810281835,  0.87434038553446281],
+    [0.85472593189256985, 0.84787488124672816,  0.8726282980930582],
+    [0.85133714570857189, 0.84672735796116472,  0.87086081657350445],
+    [0.84780710702577922, 0.8454546229209523,   0.86904036783694438],
+    [0.8441261828674842,  0.84406482711037389,  0.86716973322690072],
+    [0.84030420805957784, 0.8425605950855084,   0.865250882410458],
+    [0.83634031809191178, 0.84094796518951942,  0.86328528001070159],
+    [0.83222705712934408, 0.83923490627754482,  0.86127563500427884],
+    [0.82796894316013536, 0.83742600751395202,  0.85922399451306786],
+    [0.82357429680252847, 0.83552487764795436,  0.85713191328514948],
+    [0.81904654677937527, 0.8335364929949034,   0.85500206287010105],
+    [0.81438982121143089, 0.83146558694197847,  0.85283759062147024],
+    [0.8095999819094809,  0.82931896673505456,  0.85064441601050367],
+    [0.80469164429814577, 0.82709838780560663,  0.84842449296974021],
+    [0.79967075421267997, 0.82480781812080928,  0.84618210029578533],
+    [0.79454305089231114, 0.82245116226304615,  0.84392184786827984],
+    [0.78931445564608915, 0.82003213188702007,  0.8416486380471222],
+    [0.78399101042764918, 0.81755426400533426,  0.83936747464036732],
+    [0.77857892008227592, 0.81502089378742548,  0.8370834463093898],
+    [0.77308416590170936, 0.81243524735466011,  0.83480172950579679],
+    [0.76751108504417864, 0.8098007598713145,   0.83252816638059668],
+    [0.76186907937980286, 0.80711949387647486,  0.830266486168872],
+    [0.75616443584381976, 0.80439408733477935,  0.82802138994719998],
+    [0.75040346765406696, 0.80162699008965321,  0.82579737851082424],
+    [0.74459247771890169, 0.79882047719583249,  0.82359867586156521],
+    [0.73873771700494939, 0.79597665735031009,  0.82142922780433014],
+    [0.73284543645523459, 0.79309746468844067,  0.81929263384230377],
+    [0.72692177512829703, 0.7901846863592763,   0.81719217466726379],
+    [0.72097280665536778, 0.78723995923452639,  0.81513073920879264],
+    [0.71500403076252128, 0.78426487091581187,  0.81311116559949914],
+    [0.70902078134539304, 0.78126088716070907,  0.81113591855117928],
+    [0.7030297722540817,  0.77822904973358131,  0.80920618848056969],
+    [0.6970365443886174,  0.77517050008066057,  0.80732335380063447],
+    [0.69104641009309098, 0.77208629460678091,  0.80548841690679074],
+    [0.68506446154395928, 0.7689774029354699,   0.80370206267176914],
+    [0.67909554499882152, 0.76584472131395898,  0.8019646617300199],
+    [0.67314422559426212, 0.76268908733890484,  0.80027628545809526],
+    [0.66721479803752815, 0.7595112803730375,   0.79863674654537764],
+    [0.6613112930078745,  0.75631202708719025,  0.7970456043491897],
+    [0.65543692326454717, 0.75309208756768431,  0.79550271129031047],
+    [0.64959573004253479, 0.74985201221941766,  0.79400674021499107],
+    [0.6437910831099849,  0.7465923800833657,   0.79255653201306053],
+    [0.63802586828545982, 0.74331376714033193,  0.79115100459573173],
+    [0.6323027138710603,  0.74001672160131404,  0.78978892762640429],
+    [0.62662402022604591, 0.73670175403699445,  0.78846901316334561],
+    [0.62099193064817548, 0.73336934798923203,  0.78718994624696581],
+    [0.61540846411770478, 0.73001995232739691,  0.78595022706750484],
+    [0.60987543176093062, 0.72665398759758293,  0.78474835732694714],
+    [0.60439434200274855, 0.7232718614323369,   0.78358295593535587],
+    [0.5989665814482068,  0.71987394892246725,  0.78245259899346642],
+    [0.59359335696837223, 0.7164606049658685,   0.78135588237640097],
+    [0.58827579780555495, 0.71303214646458135,  0.78029141405636515],
+    [0.58301487036932409, 0.70958887676997473,  0.77925781820476592],
+    [0.5778116438998202,  0.70613106157153982,  0.77825345121025524],
+    [0.5726668948158774,  0.7026589535425779,   0.77727702680911992],
+    [0.56758117853861967, 0.69917279302646274,  0.77632748534275298],
+    [0.56255515357219343, 0.69567278381629649,  0.77540359142309845],
+    [0.55758940419605174, 0.69215911458254054,  0.7745041337932782],
+    [0.55268450589347129, 0.68863194515166382,  0.7736279426902245],
+    [0.54784098153018634, 0.68509142218509878,  0.77277386473440868],
+    [0.54305932424018233, 0.68153767253065878,  0.77194079697835083],
+    [0.53834015575176275, 0.67797081129095405,  0.77112734439057717],
+    [0.53368389147728401, 0.67439093705212727,  0.7703325054879735],
+    [0.529090861832473,   0.67079812302806219,  0.76955552292313134],
+    [0.52456151470593582, 0.66719242996142225,  0.76879541714230948],
+    [0.52009627392235558, 0.66357391434030388,  0.76805119403344102],
+    [0.5156955988596057,  0.65994260812897998,  0.76732191489596169],
+    [0.51135992541601927, 0.65629853981831865,  0.76660663780645333],
+    [0.50708969576451657, 0.65264172403146448,  0.76590445660835849],
+    [0.5028853540415561,  0.64897216734095264,  0.76521446718174913],
+    [0.49874733661356069, 0.6452898684900934,   0.76453578734180083],
+    [0.4946761847863938,  0.64159484119504429,  0.76386719002130909],
+    [0.49067224938561221, 0.63788704858847078,  0.76320812763163837],
+    [0.4867359599430568,  0.63416646251100506,  0.76255780085924041],
+    [0.4828677867260272,  0.6304330455306234,   0.76191537149895305],
+    [0.47906816236197386, 0.62668676251860134,  0.76128000375662419],
+    [0.47533752394906287, 0.62292757283835809,  0.76065085571817748],
+    [0.47167629518877091, 0.61915543242884641,  0.76002709227883047],
+    [0.46808490970531597, 0.61537028695790286,  0.75940789891092741],
+    [0.46456376716303932, 0.61157208822864151,  0.75879242623025811],
+    [0.46111326647023881, 0.607760777169989,    0.75817986436807139],
+    [0.45773377230160567, 0.60393630046586455,  0.75756936901859162],
+    [0.45442563977552913, 0.60009859503858665,  0.75696013660606487],
+    [0.45118918687617743, 0.59624762051353541,  0.75635120643246645],
+    [0.44802470933589172, 0.59238331452146575,  0.75574176474107924],
+    [0.44493246854215379, 0.5885055998308617,   0.7551311041857901],
+    [0.44191271766696399, 0.58461441100175571,  0.75451838884410671],
+    [0.43896563958048396, 0.58070969241098491,  0.75390276208285945],
+    [0.43609138958356369, 0.57679137998186081,  0.7532834105961016],
+    [0.43329008867358393, 0.57285941625606673,  0.75265946532566674],
+    [0.43056179073057571, 0.56891374572457176,  0.75203008099312696],
+    [0.42790652284925834, 0.5649543060909209,   0.75139443521914839],
+    [0.42532423665011354, 0.56098104959950301,  0.75075164989005116],
+    [0.42281485675772662, 0.55699392126996583,  0.75010086988227642],
+    [0.42037822361396326, 0.55299287158108168,  0.7494412559451894],
+    [0.41801414079233629, 0.54897785421888889,  0.74877193167001121],
+    [0.4157223260454232,  0.54494882715350401,  0.74809204459000522],
+    [0.41350245743314729, 0.54090574771098476,  0.74740073297543086],
+    [0.41135414697304568, 0.53684857765005933,  0.74669712855065784],
+    [0.4092768899914751,  0.53277730177130322,  0.74598030635707824],
+    [0.40727018694219069, 0.52869188011057411,  0.74524942637581271],
+    [0.40533343789303178, 0.52459228174983119,  0.74450365836708132],
+    [0.40346600333905397, 0.52047847653840029,  0.74374215223567086],
+    [0.40166714010896104, 0.51635044969688759,  0.7429640345324835],
+    [0.39993606933454834, 0.51220818143218516,  0.74216844571317986],
+    [0.3982719152586337,  0.50805166539276136,  0.74135450918099721],
+    [0.39667374905665609, 0.50388089053847973,  0.74052138580516735],
+    [0.39514058808207631, 0.49969585326377758,  0.73966820211715711],
+    [0.39367135736822567, 0.49549655777451179,  0.738794102296364],
+    [0.39226494876209317, 0.49128300332899261,  0.73789824784475078],
+    [0.39092017571994903, 0.48705520251223039,  0.73697977133881254],
+    [0.38963580160340855, 0.48281316715123496,  0.73603782546932739],
+    [0.38841053300842432, 0.47855691131792805,  0.73507157641157261],
+    [0.38724301459330251, 0.47428645933635388,  0.73408016787854391],
+    [0.38613184178892102, 0.4700018340988123,   0.7330627749243106],
+    [0.38507556793651387, 0.46570306719930193,  0.73201854033690505],
+    [0.38407269378943537, 0.46139018782416635,  0.73094665432902683],
+    [0.38312168084402748, 0.45706323581407199,  0.72984626791353258],
+    [0.38222094988570376, 0.45272225034283325,  0.72871656144003782],
+    [0.38136887930454161, 0.44836727669277859,  0.72755671317141346],
+    [0.38056380696565623, 0.44399837208633719,  0.72636587045135315],
+    [0.37980403744848751, 0.43961558821222629,  0.72514323778761092],
+    [0.37908789283110761, 0.43521897612544935,  0.72388798691323131],
+    [0.378413635091359,   0.43080859411413064,  0.72259931993061044],
+    [0.37777949753513729, 0.4263845142616835,   0.72127639993530235],
+    [0.37718371844251231, 0.42194680223454828,  0.71991841524475775],
+    [0.37662448930806297, 0.41749553747893614,  0.71852454736176108],
+    [0.37610001286385814, 0.41303079952477062,  0.71709396919920232],
+    [0.37560846919442398, 0.40855267638072096,  0.71562585091587549],
+    [0.37514802505380473, 0.4040612609993941,   0.7141193695725726],
+    [0.37471686019302231, 0.3995566498711684,   0.71257368516500463],
+    [0.37431313199312338, 0.39503894828283309,  0.71098796522377461],
+    [0.37393499330475782, 0.39050827529375831,  0.70936134293478448],
+    [0.3735806215098284,  0.38596474386057539,  0.70769297607310577],
+    [0.37324816143326384, 0.38140848555753937,  0.70598200974806036],
+    [0.37293578646665032, 0.37683963835219841,  0.70422755780589941],
+    [0.37264166757849604, 0.37225835004836849,  0.7024287314570723],
+    [0.37236397858465387, 0.36766477862108266,  0.70058463496520773],
+    [0.37210089702443822, 0.36305909736982378,  0.69869434615073722],
+    [0.3718506155898596,  0.35844148285875221,  0.69675695810256544],
+    [0.37161133234400479, 0.3538121372967869,   0.69477149919380887],
+    [0.37138124223736607, 0.34917126878479027,  0.69273703471928827],
+    [0.37115856636209105, 0.34451911410230168,  0.69065253586464992],
+    [0.37094151551337329, 0.33985591488818123,  0.68851703379505125],
+    [0.37072833279422668, 0.33518193808489577,  0.68632948169606767],
+    [0.37051738634484427, 0.33049741244307851,  0.68408888788857214],
+    [0.37030682071842685, 0.32580269697872455,  0.68179411684486679],
+    [0.37009487130772695, 0.3210981375964933,   0.67944405399056851],
+    [0.36987980329025361, 0.31638410101153364,  0.67703755438090574],
+    [0.36965987626565955, 0.31166098762951971,  0.67457344743419545],
+    [0.36943334591276228, 0.30692923551862339,  0.67205052849120617],
+    [0.36919847837592484, 0.30218932176507068,  0.66946754331614522],
+    [0.36895355306596778, 0.29744175492366276,  0.66682322089824264],
+    [0.36869682231895268, 0.29268709856150099,  0.66411625298236909],
+    [0.36842655638020444, 0.28792596437778462,  0.66134526910944602],
+    [0.36814101479899719, 0.28315901221182987,  0.65850888806972308],
+    [0.36783843696531082, 0.27838697181297761,  0.65560566838453704],
+    [0.36751707094367697, 0.27361063317090978,  0.65263411711618635],
+    [0.36717513650699446, 0.26883085667326956,  0.64959272297892245],
+    [0.36681085540107988, 0.26404857724525643,  0.64647991652908243],
+    [0.36642243251550632, 0.25926481158628106,  0.64329409140765537],
+    [0.36600853966739794, 0.25448043878086224,  0.64003361803368586],
+    [0.36556698373538982, 0.24969683475296395,  0.63669675187488584],
+    [0.36509579845886808, 0.24491536803550484,  0.63328173520055586],
+    [0.36459308890125008, 0.24013747024823828,  0.62978680155026101],
+    [0.36405693022088509, 0.23536470386204195,  0.62621013451953023],
+    [0.36348537610385145, 0.23059876218396419,  0.62254988622392882],
+    [0.36287643560041027, 0.22584149293287031,  0.61880417410823019],
+    [0.36222809558295926, 0.22109488427338303,  0.61497112346096128],
+    [0.36153829010998356, 0.21636111429594002,  0.61104880679640927],
+    [0.36080493826624654, 0.21164251793458128,  0.60703532172064711],
+    [0.36002681809096376, 0.20694122817889948,  0.60292845431916875],
+    [0.35920088560930186, 0.20226037920758122,  0.5987265295935138],
+    [0.35832489966617809, 0.197602942459778,    0.59442768517501066],
+    [0.35739663292915563, 0.19297208197842461,  0.59003011251063131],
+    [0.35641381143126327, 0.18837119869242164,  0.5855320765920552],
+    [0.35537415306906722, 0.18380392577704466,  0.58093191431832802],
+    [0.35427534960663759, 0.17927413271618647,  0.57622809660668717],
+    [0.35311574421123737, 0.17478570377561287,  0.57141871523555288],
+    [0.35189248608873791, 0.17034320478524959,  0.56650284911216653],
+    [0.35060304441931012, 0.16595129984720861,  0.56147964703993225],
+    [0.34924513554955644, 0.16161477763045118,  0.55634837474163779],
+    [0.34781653238777782, 0.15733863511152979,  0.55110853452703257],
+    [0.34631507175793091, 0.15312802296627787,  0.5457599924248665],
+    [0.34473901574536375, 0.14898820589826409,  0.54030245920406539],
+    [0.34308600291572294, 0.14492465359918028,  0.53473704282067103],
+    [0.34135411074506483, 0.1409427920655632,   0.52906500940336754],
+    [0.33954168752669694, 0.13704801896718169,  0.52328797535085236],
+    [0.33764732090671112, 0.13324562282438077,  0.51740807573979475],
+    [0.33566978565015315, 0.12954074251271822,  0.51142807215168951],
+    [0.33360804901486002, 0.12593818301005921,  0.50535164796654897],
+    [0.33146154891145124, 0.12244245263391232,  0.49918274588431072],
+    [0.32923005203231409, 0.11905764321981127,  0.49292595612342666],
+    [0.3269137124539796,  0.1157873496841953,   0.48658646495697461],
+    [0.32451307931207785, 0.11263459791730848,  0.48017007211645196],
+    [0.32202882276069322, 0.10960114111258401,  0.47368494725726878],
+    [0.31946262395497965, 0.10668879882392659,  0.46713728801395243],
+    [0.31681648089023501, 0.10389861387653518,  0.46053414662739794],
+    [0.31409278414755532, 0.10123077676403242,  0.45388335612058467],
+    [0.31129434479712365, 0.098684771934052201, 0.44719313715161618],
+    [0.30842444457210105, 0.096259385340577736, 0.44047194882050544],
+    [0.30548675819945936, 0.093952764840823738, 0.43372849999361113],
+    [0.30248536364574252, 0.091761187397303601, 0.42697404043749887],
+    [0.29942483960214772, 0.089682253716750038, 0.42021619665853854],
+    [0.29631000388905288, 0.087713250960463951, 0.41346259134143476],
+    [0.29314593096985248, 0.085850656889620708, 0.40672178082365834],
+    [0.28993792445176608, 0.08409078829085731,  0.40000214725256295],
+    [0.28669151388283165, 0.082429873848480689, 0.39331182532243375],
+    [0.28341239797185225, 0.080864153365499375, 0.38665868550105914],
+    [0.28010638576975472, 0.079389994802261526, 0.38005028528138707],
+    [0.27677939615815589, 0.078003941033788216, 0.37349382846504675],
+    [0.27343739342450812, 0.076702800237496066, 0.36699616136347685],
+    [0.27008637749114051, 0.075483675584275545, 0.36056376228111864],
+    [0.26673233211995284, 0.074344018028546205, 0.35420276066240958],
+    [0.26338121807151404, 0.073281657939897077, 0.34791888996380105],
+    [0.26003895187439957, 0.072294781043362205, 0.3417175669546984],
+    [0.25671191651083902, 0.071380106242082242, 0.33560648984600089],
+    [0.25340685873736807, 0.070533582926851829, 0.3295945757321303],
+    [0.25012845306199383, 0.069758206429106989, 0.32368100685760637],
+    [0.24688226237958999, 0.069053639449204451, 0.31786993834254956],
+    [0.24367372557466271, 0.068419855150922693, 0.31216524050888372],
+    [0.24050813332295939, 0.067857103814855602, 0.30657054493678321],
+    [0.23739062429054825, 0.067365888050555517, 0.30108922184065873],
+    [0.23433055727563878, 0.066935599661639394, 0.29574009929867601],
+    [0.23132955273021344, 0.066576186939090592, 0.29051361067988485],
+    [0.2283917709422868,  0.06628997924139618,  0.28541074411068496],
+    [0.22552164337737857, 0.066078173119395595, 0.28043398847505197],
+    [0.22272706739121817, 0.065933790675651943, 0.27559714652053702],
+    [0.22001251100779617, 0.065857918918907604, 0.27090279994325861],
+    [0.21737845072382705, 0.065859661233562045, 0.26634209349669508],
+    [0.21482843531473683, 0.065940385613778491, 0.26191675992376573],
+    [0.21237411048541005, 0.066085024661758446, 0.25765165093569542],
+    [0.21001214221188125, 0.066308573918947178, 0.2535289048041211],
+    [0.2077442377448806,  0.06661453200418091,  0.24954644291943817],
+    [0.20558051999470117, 0.066990462397868739, 0.24572497420147632],
+    [0.20352007949514977, 0.067444179612424215, 0.24205576625191821],
+    [0.20156133764129841, 0.067983271026200248, 0.23852974228695395],
+    [0.19971571438603364, 0.068592710553704722, 0.23517094067076993],
+    [0.19794834061899208, 0.069314066071660657, 0.23194647381302336],
+    [0.1960826032659409,  0.070321227242423623, 0.22874673279569585],
+    [0.19410351363791453, 0.071608304856891569, 0.22558727307410353],
+    [0.19199449184606268, 0.073182830649273306, 0.22243385243433622],
+    [0.18975853639094634, 0.075019861862143766, 0.2193005075652994],
+    [0.18739228342697645, 0.077102096899588329, 0.21618875376309582],
+    [0.18488035509396164, 0.079425730279723883, 0.21307651648984993],
+    [0.18774482037046955, 0.077251588468039312, 0.21387448578597812],
+    [0.19049578401722037, 0.075311278416787641, 0.2146562337112265],
+    [0.1931548636579131,  0.073606819040117955, 0.21542362939081539],
+    [0.19571853588267552, 0.072157781039602742, 0.21617499187076789],
+    [0.19819343656336558, 0.070974625252738788, 0.21690975060032436],
+    [0.20058760685133747, 0.070064576149984209, 0.21762721310371608],
+    [0.20290365333558247, 0.069435248580458964, 0.21833167885096033],
+    [0.20531725273301316, 0.068919592266397572, 0.21911516689288835],
+    [0.20785704662965598, 0.068484398797025281, 0.22000133917653536],
+    [0.21052882914958676, 0.06812195249816172,  0.22098759107715404],
+    [0.2133313859647627,  0.067830148426026665, 0.22207043213024291],
+    [0.21625279838647882, 0.067616330270516389, 0.22324568672294431],
+    [0.21930503925136402, 0.067465786362940039, 0.22451023616807558],
+    [0.22247308588973624, 0.067388214053092838, 0.22585960379408354],
+    [0.2257539681670791,  0.067382132300147474, 0.22728984778098055],
+    [0.22915620278592841, 0.067434730871152565, 0.22879681433956656],
+    [0.23266299920501882, 0.067557104388479783, 0.23037617493752832],
+    [0.23627495835774248, 0.06774359820987802,  0.23202360805926608],
+    [0.23999586188690308, 0.067985029964779953, 0.23373434258507808],
+    [0.24381149720247919, 0.068289851529011875, 0.23550427698321885],
+    [0.24772092990501099, 0.068653337909486523, 0.2373288009471749],
+    [0.25172899728289466, 0.069064630826035506, 0.23920260612763083],
+    [0.25582135547481771, 0.06953231029187984,  0.24112190491594204],
+    [0.25999463887892144, 0.070053855603861875, 0.24308218808684579],
+    [0.26425512207060942, 0.070616595622995437, 0.24507758869355967],
+    [0.26859095948172862, 0.071226716277922458, 0.24710443563450618],
+    [0.27299701518897301, 0.071883555446163511, 0.24915847093232929],
+    [0.27747150809142801, 0.072582969899254779, 0.25123493995942769],
+    [0.28201746297366942, 0.073315693214040967, 0.25332800295084507],
+    [0.28662309235899847, 0.074088460826808866, 0.25543478673717029],
+    [0.29128515387578635, 0.074899049847466703, 0.25755101595750435],
+    [0.2960004726065818,  0.075745336000958424, 0.25967245030364566],
+    [0.30077276812918691, 0.076617824336164764, 0.26179294097819672],
+    [0.30559226007249934, 0.077521963107537312, 0.26391006692119662],
+    [0.31045520848595526, 0.078456871676182177, 0.2660200572779356],
+    [0.31535870009205808, 0.079420997315243186, 0.26811904076941961],
+    [0.32029986557994061, 0.080412994737554838, 0.27020322893039511],
+    [0.32527888860401261, 0.081428390076546092, 0.27226772884656186],
+    [0.33029174471181438, 0.08246763389003825,  0.27430929404579435],
+    [0.33533353224455448, 0.083532434119003962, 0.27632534356790039],
+    [0.34040164359597463, 0.084622236191702671, 0.27831254595259397],
+    [0.34549355713871799, 0.085736654965126335, 0.28026769921081435],
+    [0.35060678246032478, 0.08687555176033529,  0.28218770540182386],
+    [0.35573889947341125, 0.088038974350243354, 0.2840695897279818],
+    [0.36088752387578377, 0.089227194362745205, 0.28591050458531014],
+    [0.36605031412464006, 0.090440685427697898, 0.2877077458811747],
+    [0.37122508431309342, 0.091679997480262732, 0.28945865397633169],
+    [0.3764103053221462,  0.092945198093777909, 0.29116024157313919],
+    [0.38160247377467543, 0.094238731263712183, 0.29281107506269488],
+    [0.38679939079544168, 0.09556181960083443,  0.29440901248173756],
+    [0.39199887556812907, 0.09691583650296684,  0.29595212005509081],
+    [0.39719876876325577, 0.098302320968278623, 0.29743856476285779],
+    [0.40239692379737496, 0.099722930314950553, 0.29886674369733968],
+    [0.40759120392688708, 0.10117945586419633,  0.30023519507728602],
+    [0.41277985630360303, 0.1026734006932461,   0.30154226437468967],
+    [0.41796105205173684, 0.10420644885760968,  0.30278652039631843],
+    [0.42313214269556043, 0.10578120994917611,  0.3039675809469457],
+    [0.42829101315789753, 0.1073997763055258,   0.30508479060294547],
+    [0.4334355841041439,  0.1090642347484701,   0.30613767928289148],
+    [0.43856378187931538, 0.11077667828375456,  0.30712600062348083],
+    [0.44367358645071275, 0.11253912421257944,  0.30804973095465449],
+    [0.44876299173174822, 0.11435355574622549,  0.30890905921943196],
+    [0.45383005086999889, 0.11622183788331528,  0.30970441249844921],
+    [0.45887288947308297, 0.11814571137706886,  0.31043636979038808],
+    [0.46389102840284874, 0.12012561256850712,  0.31110343446582983],
+    [0.46888111384598413, 0.12216445576414045,  0.31170911458932665],
+    [0.473841437035254,   0.12426354237989065,  0.31225470169927194],
+    [0.47877034239726296, 0.12642401401409453,  0.31274172735821959],
+    [0.48366628618847957, 0.12864679022013889,  0.31317188565991266],
+    [0.48852847371852987, 0.13093210934893723,  0.31354553695453014],
+    [0.49335504375145617, 0.13328091630401023,  0.31386561956734976],
+    [0.49814435462074153, 0.13569380302451714,  0.314135190862664],
+    [0.50289524974970612, 0.13817086581280427,  0.31435662153833671],
+    [0.50760681181053691, 0.14071192654913128,  0.31453200120082569],
+    [0.51227835105321762, 0.14331656120063752,  0.3146630922831542],
+    [0.51690848800544464, 0.14598463068714407,  0.31475407592280041],
+    [0.52149652863229956, 0.14871544765633712,  0.31480767954534428],
+    [0.52604189625477482, 0.15150818660835483,  0.31482653406646727],
+    [0.53054420489856446, 0.15436183633886777,  0.31481299789187128],
+    [0.5350027976174474,  0.15727540775107324,  0.31477085207396532],
+    [0.53941736649199057, 0.16024769309971934,  0.31470295028655965],
+    [0.54378771313608565, 0.16327738551419116,  0.31461204226295625],
+    [0.54811370033467621, 0.1663630904279047,   0.31450102990914708],
+    [0.55239521572711914, 0.16950338809328983,  0.31437291554615371],
+    [0.55663229034969341, 0.17269677158182117,  0.31423043195101424],
+    [0.56082499039117173, 0.17594170887918095,  0.31407639883970623],
+    [0.56497343529017696, 0.17923664950367169,  0.3139136046337036],
+    [0.56907784784011428, 0.18258004462335425,  0.31374440956796529],
+    [0.57313845754107873, 0.18597036007065024,  0.31357126868520002],
+    [0.57715550812992045, 0.18940601489760422,  0.31339704333572083],
+    [0.58112932761586555, 0.19288548904692518,  0.31322399394183942],
+    [0.58506024396466882, 0.19640737049066315,  0.31305401163732732],
+    [0.58894861935544707, 0.19997020971775276,  0.31288922211590126],
+    [0.59279480536520257, 0.20357251410079796,  0.31273234839304942],
+    [0.59659918109122367, 0.207212956082026,    0.31258523031121233],
+    [0.60036213010411577, 0.21089030138947745,  0.31244934410414688],
+    [0.60408401696732739, 0.21460331490206347,  0.31232652641170694],
+    [0.60776523994818654, 0.21835070166659282,  0.31221903291870201],
+    [0.6114062072731884,  0.22213124697023234,  0.31212881396435238],
+    [0.61500723236391375, 0.22594402043981826,  0.31205680685765741],
+    [0.61856865258877192, 0.22978799249179921,  0.31200463838728931],
+    [0.62209079821082613, 0.2336621873300741,   0.31197383273627388],
+    [0.62557416500434959, 0.23756535071152696,  0.31196698314912269],
+    [0.62901892016985872, 0.24149689191922535,  0.31198447195645718],
+    [0.63242534854210275, 0.24545598775548677,  0.31202765974624452],
+    [0.6357937104834237,  0.24944185818822678,  0.31209793953300591],
+    [0.6391243387840212,  0.25345365461983138,  0.31219689612063978],
+    [0.642417577481186,   0.257490519876798,    0.31232631707560987],
+    [0.64567349382645434, 0.26155203161615281,  0.31248673753935263],
+    [0.64889230169458245, 0.26563755336209077,  0.31267941819570189],
+    [0.65207417290277303, 0.26974650525236699,  0.31290560605819168],
+    [0.65521932609327127, 0.27387826652410152,  0.3131666792687211],
+    [0.6583280801134499,  0.27803210957665631,  0.3134643447952643],
+    [0.66140037532601781, 0.28220778870555907,  0.31379912926498488],
+    [0.66443632469878844, 0.28640483614256179,  0.31417223403606975],
+    [0.66743603766369131, 0.29062280081258873,  0.31458483752056837],
+    [0.67039959547676198, 0.29486126309253047,  0.31503813956872212],
+    [0.67332725564817331, 0.29911962764489264,  0.31553372323982209],
+    [0.67621897924409746, 0.30339762792450425,  0.3160724937230589],
+    [0.67907474028157344, 0.30769497879760166,  0.31665545668946665],
+    [0.68189457150944521, 0.31201133280550686,  0.31728380489244951],
+    [0.68467850942494535, 0.31634634821222207,  0.31795870784057567],
+    [0.68742656435169625, 0.32069970535138104,  0.31868137622277692],
+    [0.6901389321505248,  0.32507091815606004,  0.31945332332898302],
+    [0.69281544846764931, 0.32945984647042675,  0.3202754315314667],
+    [0.69545608346891119, 0.33386622163232865,  0.32114884306985791],
+    [0.6980608153581771,  0.33828976326048621,  0.32207478855218091],
+    [0.70062962477242097, 0.34273019305341756,  0.32305449047765694],
+    [0.70316249458814151, 0.34718723719597999,  0.32408913679491225],
+    [0.70565951122610093, 0.35166052978120937,  0.32518014084085567],
+    [0.70812059568420482, 0.35614985523380299,  0.32632861885644465],
+    [0.7105456546582587,  0.36065500290840113,  0.32753574162788762],
+    [0.71293466839773467, 0.36517570519856757,  0.3288027427038317],
+    [0.71528760614847287, 0.36971170225223449,  0.3301308728723546],
+    [0.71760444908133847, 0.37426272710686193,  0.33152138620958932],
+    [0.71988521490549851, 0.37882848839337313,  0.33297555200245399],
+    [0.7221299918421461,  0.38340864508963057,  0.33449469983585844],
+    [0.72433865647781592, 0.38800301593162145,  0.33607995965691828],
+    [0.72651122900227549, 0.3926113126792577,   0.3377325942005665],
+    [0.72864773856716547, 0.39723324476747235,  0.33945384341064017],
+    [0.73074820754845171, 0.401868526884681,    0.3412449533046818],
+    [0.73281270506268747, 0.4065168468778026,   0.34310715173410822],
+    [0.73484133598564938, 0.41117787004519513,  0.34504169470809071],
+    [0.73683422173585866, 0.41585125850290111,  0.34704978520758401],
+    [0.73879140024599266, 0.42053672992315327,  0.34913260148542435],
+    [0.74071301619506091, 0.4252339389526239,   0.35129130890802607],
+    [0.7425992159973317,  0.42994254036133867,  0.35352709245374592],
+    [0.74445018676570673, 0.43466217184617112,  0.35584108091122535],
+    [0.74626615789163442, 0.43939245044973502,  0.35823439142300639],
+    [0.74804739275559562, 0.44413297780351974,  0.36070813602540136],
+    [0.74979420547170472, 0.44888333481548809,  0.36326337558360278],
+    [0.75150685045891663, 0.45364314496866825,  0.36590112443835765],
+    [0.75318566369046569, 0.45841199172949604,  0.36862236642234769],
+    [0.75483105066959544, 0.46318942799460555,  0.3714280448394211],
+    [0.75644341577140706, 0.46797501437948458,  0.37431909037543515],
+    [0.75802325538455839, 0.4727682731566229,   0.37729635531096678],
+    [0.75957111105340058, 0.47756871222057079,  0.380360657784311],
+    [0.7610876378057071,  0.48237579130289127,  0.38351275723852291],
+    [0.76257333554052609, 0.48718906673415824,  0.38675335037837993],
+    [0.76402885609288662, 0.49200802533379656,  0.39008308392311997],
+    [0.76545492593330511, 0.49683212909727231,  0.39350254000115381],
+    [0.76685228950643891, 0.5016608471009063,   0.39701221751773474],
+    [0.76822176599735303, 0.50649362371287909,  0.40061257089416885],
+    [0.7695642334401418,  0.5113298901696085,   0.40430398069682483],
+    [0.77088091962302474, 0.51616892643469103,  0.40808667584648967],
+    [0.77217257229605551, 0.5210102658711383,   0.41196089987122869],
+    [0.77344021829889886, 0.52585332093451564,  0.41592679539764366],
+    [0.77468494746063199, 0.53069749384776732,  0.41998440356963762],
+    [0.77590790730685699, 0.53554217882461186,  0.42413367909988375],
+    [0.7771103295521099,  0.54038674910561235,  0.42837450371258479],
+    [0.77829345807633121, 0.54523059488426595,  0.432706647838971],
+    [0.77945862731506643, 0.55007308413977274,  0.43712979856444761],
+    [0.78060774749483774, 0.55491335744890613,  0.44164332426364639],
+    [0.78174180478981836, 0.55975098052594863,  0.44624687186865436],
+    [0.78286225264440912, 0.56458533111166875,  0.45093985823706345],
+    [0.78397060836414478, 0.56941578326710418,  0.45572154742892063],
+    [0.78506845019606841, 0.5742417003617839,   0.46059116206904965],
+    [0.78615737132332963, 0.5790624629815756,   0.46554778281918402],
+    [0.78723904108188347, 0.58387743744557208,  0.47059039582133383],
+    [0.78831514045623963, 0.58868600173562435,  0.47571791879076081],
+    [0.78938737766251943, 0.5934875421745599,   0.48092913815357724],
+    [0.79045776847727878, 0.59828134277062461,  0.48622257801969754],
+    [0.79152832843475607, 0.60306670593147205,  0.49159667021646397],
+    [0.79260034304237448, 0.60784322087037024,  0.49705020621532009],
+    [0.79367559698664958, 0.61261029334072192,  0.50258161291269432],
+    [0.79475585972654039, 0.61736734400220705,  0.50818921213102985],
+    [0.79584292379583765, 0.62211378808451145,  0.51387124091909786],
+    [0.79693854719951607, 0.62684905679296699,  0.5196258425240281],
+    [0.79804447815136637, 0.63157258225089552,  0.52545108144834785],
+    [0.7991624518501963,  0.63628379372029187,  0.53134495942561433],
+    [0.80029415389753977, 0.64098213306749863,  0.53730535185141037],
+    [0.80144124292560048, 0.64566703459218766,  0.5433300863249918],
+    [0.80260531146112946, 0.65033793748103852,  0.54941691584603647],
+    [0.80378792531077625, 0.65499426549472628,  0.55556350867083815],
+    [0.80499054790810298, 0.65963545027564163,  0.56176745110546977],
+    [0.80621460526927058, 0.66426089585282289,  0.56802629178649788],
+    [0.8074614045096935,  0.6688700095398864,   0.57433746373459582],
+    [0.80873219170089694, 0.67346216702194517,  0.58069834805576737],
+    [0.81002809466520687, 0.67803672673971815,  0.58710626908082753],
+    [0.81135014011763329, 0.68259301546243389,  0.59355848909050757],
+    [0.81269922039881493, 0.68713033714618876,  0.60005214820435104],
+    [0.81407611046993344, 0.69164794791482131,  0.6065843782630862],
+    [0.81548146627279483, 0.69614505508308089,  0.61315221209322646],
+    [0.81691575775055891, 0.70062083014783982,  0.61975260637257923],
+    [0.81837931164498223, 0.70507438189635097,  0.62638245478933297],
+    [0.81987230650455289, 0.70950474978787481,  0.63303857040067113],
+    [0.8213947205565636,  0.7139109141951604,   0.63971766697672761],
+    [0.82294635110428427, 0.71829177331290062,  0.6464164243818421],
+    [0.8245268129450285,  0.72264614312088882,  0.65313137915422603],
+    [0.82613549710580259, 0.72697275518238258,  0.65985900156216504],
+    [0.8277716072353446,  0.73127023324078089,  0.66659570204682972],
+    [0.82943407816481474, 0.7355371221572935,   0.67333772009301907],
+    [0.83112163529096306, 0.73977184647638616,  0.68008125203631464],
+    [0.83283277185777982, 0.74397271817459876,  0.68682235874648545],
+    [0.8345656905566583,  0.7481379479992134,   0.69355697649863846],
+    [0.83631898844737929, 0.75226548952875261,  0.70027999028864962],
+    [0.83809123476131964, 0.75635314860808633,  0.70698561390212977],
+    [0.83987839884120874, 0.76039907199779677,  0.71367147811129228],
+    [0.84167750766845151, 0.76440101200982946,  0.72033299387284622],
+    [0.84348529222933699, 0.76835660399870176,  0.72696536998972039],
+    [0.84529810731955113, 0.77226338601044719,  0.73356368240541492],
+    [0.84711195507965098, 0.77611880236047159,  0.74012275762807056],
+    [0.84892245563117641, 0.77992021407650147,  0.74663719293664366],
+    [0.85072697023178789, 0.78366457342383888,  0.7530974636118285],
+    [0.85251907207708444, 0.78734936133548439,  0.7594994148789691],
+    [0.85429219611470464, 0.79097196777091994,  0.76583801477914104],
+    [0.85604022314725403, 0.79452963601550608,  0.77210610037674143],
+    [0.85775662943504905, 0.79801963142713928,  0.77829571667247499],
+    [0.8594346370300241,  0.8014392309950078,   0.78439788751383921],
+    [0.86107117027565516, 0.80478517909812231,  0.79039529663736285],
+    [0.86265601051127572, 0.80805523804261525,  0.796282666437655],
+    [0.86418343723941027, 0.81124644224653542,  0.80204612696863953],
+    [0.86564934325605325, 0.81435544067514909,  0.80766972324164554],
+    [0.86705314907048503, 0.81737804041911244,  0.81313419626911398],
+    [0.86839954695818633, 0.82030875512181523,  0.81841638963128993],
+    [0.86969131502613806, 0.82314158859569164,  0.82350476683173168],
+    [0.87093846717297507, 0.82586857889438514,  0.82838497261149613],
+    [0.87215331978454325, 0.82848052823709672,  0.8330486712880828],
+    [0.87335171360916275, 0.83096715251272624,  0.83748851001197089],
+    [0.87453793320260187, 0.83331972948645461,  0.84171925358069011],
+    [0.87571458709961403, 0.8355302318472394,   0.84575537519027078],
+    [0.87687848451614692, 0.83759238071186537,  0.84961373549150254],
+    [0.87802298436649007, 0.83950165618540074,  0.85330645352458923],
+    [0.87913244240792765, 0.84125554884475906,  0.85685572291039636],
+    [0.88019293315695812, 0.84285224824778615,  0.86027399927156634],
+    [0.88119169871341951, 0.84429066717717349,  0.86356595168669881],
+    [0.88211542489401606, 0.84557007254559347,  0.86673765046233331],
+    [0.88295168595448525, 0.84668970275699273,  0.86979617048190971],
+    [0.88369127145898041, 0.84764891761519268,  0.87274147101441557],
+    [0.88432713054113543, 0.84844741572055415,  0.87556785228242973],
+    [0.88485138159908572, 0.84908426422893801,  0.87828235285372469],
+    [0.88525897972630474, 0.84955892810989209,  0.88088414794024839],
+    [0.88554714811952384, 0.84987174283631584,  0.88336206121170946],
+    [0.88571155122845646, 0.85002186115856315,  0.88572538990087124]]
+
+_twilight_shifted_data = (_twilight_data[len(_twilight_data)//2:] +
+                          _twilight_data[:len(_twilight_data)//2])
+_twilight_shifted_data.reverse()
+
+cmaps = {
+    name: ListedColormap(data, name=name) for name, data in [
+        ('magma', _magma_data),
+        ('inferno', _inferno_data),
+        ('plasma', _plasma_data),
+        ('viridis', _viridis_data),
+        ('cividis', _cividis_data),
+        ('twilight', _twilight_data),
+        ('twilight_shifted', _twilight_shifted_data),
+    ]}

+ 1147 - 0
venv/lib/python3.8/site-packages/matplotlib/_color_data.py

@@ -0,0 +1,1147 @@
+from collections import OrderedDict
+
+
+BASE_COLORS = {
+    'b': (0, 0, 1),        # blue
+    'g': (0, 0.5, 0),      # green
+    'r': (1, 0, 0),        # red
+    'c': (0, 0.75, 0.75),  # cyan
+    'm': (0.75, 0, 0.75),  # magenta
+    'y': (0.75, 0.75, 0),  # yellow
+    'k': (0, 0, 0),        # black
+    'w': (1, 1, 1),        # white
+}
+
+
+# These colors are from Tableau
+TABLEAU_COLORS = (
+    ('blue', '#1f77b4'),
+    ('orange', '#ff7f0e'),
+    ('green', '#2ca02c'),
+    ('red', '#d62728'),
+    ('purple', '#9467bd'),
+    ('brown', '#8c564b'),
+    ('pink', '#e377c2'),
+    ('gray', '#7f7f7f'),
+    ('olive', '#bcbd22'),
+    ('cyan', '#17becf'),
+)
+
+# Normalize name to "tab:<name>" to avoid name collisions.
+TABLEAU_COLORS = OrderedDict(
+    ('tab:' + name, value) for name, value in TABLEAU_COLORS)
+
+# This mapping of color names -> hex values is taken from
+# a survey run by Randall Munroe see:
+# https://blog.xkcd.com/2010/05/03/color-survey-results/
+# for more details.  The results are hosted at
+# https://xkcd.com/color/rgb
+# and also available as a text file at
+# https://xkcd.com/color/rgb.txt
+#
+# License: http://creativecommons.org/publicdomain/zero/1.0/
+XKCD_COLORS = {
+    'cloudy blue': '#acc2d9',
+    'dark pastel green': '#56ae57',
+    'dust': '#b2996e',
+    'electric lime': '#a8ff04',
+    'fresh green': '#69d84f',
+    'light eggplant': '#894585',
+    'nasty green': '#70b23f',
+    'really light blue': '#d4ffff',
+    'tea': '#65ab7c',
+    'warm purple': '#952e8f',
+    'yellowish tan': '#fcfc81',
+    'cement': '#a5a391',
+    'dark grass green': '#388004',
+    'dusty teal': '#4c9085',
+    'grey teal': '#5e9b8a',
+    'macaroni and cheese': '#efb435',
+    'pinkish tan': '#d99b82',
+    'spruce': '#0a5f38',
+    'strong blue': '#0c06f7',
+    'toxic green': '#61de2a',
+    'windows blue': '#3778bf',
+    'blue blue': '#2242c7',
+    'blue with a hint of purple': '#533cc6',
+    'booger': '#9bb53c',
+    'bright sea green': '#05ffa6',
+    'dark green blue': '#1f6357',
+    'deep turquoise': '#017374',
+    'green teal': '#0cb577',
+    'strong pink': '#ff0789',
+    'bland': '#afa88b',
+    'deep aqua': '#08787f',
+    'lavender pink': '#dd85d7',
+    'light moss green': '#a6c875',
+    'light seafoam green': '#a7ffb5',
+    'olive yellow': '#c2b709',
+    'pig pink': '#e78ea5',
+    'deep lilac': '#966ebd',
+    'desert': '#ccad60',
+    'dusty lavender': '#ac86a8',
+    'purpley grey': '#947e94',
+    'purply': '#983fb2',
+    'candy pink': '#ff63e9',
+    'light pastel green': '#b2fba5',
+    'boring green': '#63b365',
+    'kiwi green': '#8ee53f',
+    'light grey green': '#b7e1a1',
+    'orange pink': '#ff6f52',
+    'tea green': '#bdf8a3',
+    'very light brown': '#d3b683',
+    'egg shell': '#fffcc4',
+    'eggplant purple': '#430541',
+    'powder pink': '#ffb2d0',
+    'reddish grey': '#997570',
+    'baby shit brown': '#ad900d',
+    'liliac': '#c48efd',
+    'stormy blue': '#507b9c',
+    'ugly brown': '#7d7103',
+    'custard': '#fffd78',
+    'darkish pink': '#da467d',
+    'deep brown': '#410200',
+    'greenish beige': '#c9d179',
+    'manilla': '#fffa86',
+    'off blue': '#5684ae',
+    'battleship grey': '#6b7c85',
+    'browny green': '#6f6c0a',
+    'bruise': '#7e4071',
+    'kelley green': '#009337',
+    'sickly yellow': '#d0e429',
+    'sunny yellow': '#fff917',
+    'azul': '#1d5dec',
+    'darkgreen': '#054907',
+    'green/yellow': '#b5ce08',
+    'lichen': '#8fb67b',
+    'light light green': '#c8ffb0',
+    'pale gold': '#fdde6c',
+    'sun yellow': '#ffdf22',
+    'tan green': '#a9be70',
+    'burple': '#6832e3',
+    'butterscotch': '#fdb147',
+    'toupe': '#c7ac7d',
+    'dark cream': '#fff39a',
+    'indian red': '#850e04',
+    'light lavendar': '#efc0fe',
+    'poison green': '#40fd14',
+    'baby puke green': '#b6c406',
+    'bright yellow green': '#9dff00',
+    'charcoal grey': '#3c4142',
+    'squash': '#f2ab15',
+    'cinnamon': '#ac4f06',
+    'light pea green': '#c4fe82',
+    'radioactive green': '#2cfa1f',
+    'raw sienna': '#9a6200',
+    'baby purple': '#ca9bf7',
+    'cocoa': '#875f42',
+    'light royal blue': '#3a2efe',
+    'orangeish': '#fd8d49',
+    'rust brown': '#8b3103',
+    'sand brown': '#cba560',
+    'swamp': '#698339',
+    'tealish green': '#0cdc73',
+    'burnt siena': '#b75203',
+    'camo': '#7f8f4e',
+    'dusk blue': '#26538d',
+    'fern': '#63a950',
+    'old rose': '#c87f89',
+    'pale light green': '#b1fc99',
+    'peachy pink': '#ff9a8a',
+    'rosy pink': '#f6688e',
+    'light bluish green': '#76fda8',
+    'light bright green': '#53fe5c',
+    'light neon green': '#4efd54',
+    'light seafoam': '#a0febf',
+    'tiffany blue': '#7bf2da',
+    'washed out green': '#bcf5a6',
+    'browny orange': '#ca6b02',
+    'nice blue': '#107ab0',
+    'sapphire': '#2138ab',
+    'greyish teal': '#719f91',
+    'orangey yellow': '#fdb915',
+    'parchment': '#fefcaf',
+    'straw': '#fcf679',
+    'very dark brown': '#1d0200',
+    'terracota': '#cb6843',
+    'ugly blue': '#31668a',
+    'clear blue': '#247afd',
+    'creme': '#ffffb6',
+    'foam green': '#90fda9',
+    'grey/green': '#86a17d',
+    'light gold': '#fddc5c',
+    'seafoam blue': '#78d1b6',
+    'topaz': '#13bbaf',
+    'violet pink': '#fb5ffc',
+    'wintergreen': '#20f986',
+    'yellow tan': '#ffe36e',
+    'dark fuchsia': '#9d0759',
+    'indigo blue': '#3a18b1',
+    'light yellowish green': '#c2ff89',
+    'pale magenta': '#d767ad',
+    'rich purple': '#720058',
+    'sunflower yellow': '#ffda03',
+    'green/blue': '#01c08d',
+    'leather': '#ac7434',
+    'racing green': '#014600',
+    'vivid purple': '#9900fa',
+    'dark royal blue': '#02066f',
+    'hazel': '#8e7618',
+    'muted pink': '#d1768f',
+    'booger green': '#96b403',
+    'canary': '#fdff63',
+    'cool grey': '#95a3a6',
+    'dark taupe': '#7f684e',
+    'darkish purple': '#751973',
+    'true green': '#089404',
+    'coral pink': '#ff6163',
+    'dark sage': '#598556',
+    'dark slate blue': '#214761',
+    'flat blue': '#3c73a8',
+    'mushroom': '#ba9e88',
+    'rich blue': '#021bf9',
+    'dirty purple': '#734a65',
+    'greenblue': '#23c48b',
+    'icky green': '#8fae22',
+    'light khaki': '#e6f2a2',
+    'warm blue': '#4b57db',
+    'dark hot pink': '#d90166',
+    'deep sea blue': '#015482',
+    'carmine': '#9d0216',
+    'dark yellow green': '#728f02',
+    'pale peach': '#ffe5ad',
+    'plum purple': '#4e0550',
+    'golden rod': '#f9bc08',
+    'neon red': '#ff073a',
+    'old pink': '#c77986',
+    'very pale blue': '#d6fffe',
+    'blood orange': '#fe4b03',
+    'grapefruit': '#fd5956',
+    'sand yellow': '#fce166',
+    'clay brown': '#b2713d',
+    'dark blue grey': '#1f3b4d',
+    'flat green': '#699d4c',
+    'light green blue': '#56fca2',
+    'warm pink': '#fb5581',
+    'dodger blue': '#3e82fc',
+    'gross green': '#a0bf16',
+    'ice': '#d6fffa',
+    'metallic blue': '#4f738e',
+    'pale salmon': '#ffb19a',
+    'sap green': '#5c8b15',
+    'algae': '#54ac68',
+    'bluey grey': '#89a0b0',
+    'greeny grey': '#7ea07a',
+    'highlighter green': '#1bfc06',
+    'light light blue': '#cafffb',
+    'light mint': '#b6ffbb',
+    'raw umber': '#a75e09',
+    'vivid blue': '#152eff',
+    'deep lavender': '#8d5eb7',
+    'dull teal': '#5f9e8f',
+    'light greenish blue': '#63f7b4',
+    'mud green': '#606602',
+    'pinky': '#fc86aa',
+    'red wine': '#8c0034',
+    'shit green': '#758000',
+    'tan brown': '#ab7e4c',
+    'darkblue': '#030764',
+    'rosa': '#fe86a4',
+    'lipstick': '#d5174e',
+    'pale mauve': '#fed0fc',
+    'claret': '#680018',
+    'dandelion': '#fedf08',
+    'orangered': '#fe420f',
+    'poop green': '#6f7c00',
+    'ruby': '#ca0147',
+    'dark': '#1b2431',
+    'greenish turquoise': '#00fbb0',
+    'pastel red': '#db5856',
+    'piss yellow': '#ddd618',
+    'bright cyan': '#41fdfe',
+    'dark coral': '#cf524e',
+    'algae green': '#21c36f',
+    'darkish red': '#a90308',
+    'reddy brown': '#6e1005',
+    'blush pink': '#fe828c',
+    'camouflage green': '#4b6113',
+    'lawn green': '#4da409',
+    'putty': '#beae8a',
+    'vibrant blue': '#0339f8',
+    'dark sand': '#a88f59',
+    'purple/blue': '#5d21d0',
+    'saffron': '#feb209',
+    'twilight': '#4e518b',
+    'warm brown': '#964e02',
+    'bluegrey': '#85a3b2',
+    'bubble gum pink': '#ff69af',
+    'duck egg blue': '#c3fbf4',
+    'greenish cyan': '#2afeb7',
+    'petrol': '#005f6a',
+    'royal': '#0c1793',
+    'butter': '#ffff81',
+    'dusty orange': '#f0833a',
+    'off yellow': '#f1f33f',
+    'pale olive green': '#b1d27b',
+    'orangish': '#fc824a',
+    'leaf': '#71aa34',
+    'light blue grey': '#b7c9e2',
+    'dried blood': '#4b0101',
+    'lightish purple': '#a552e6',
+    'rusty red': '#af2f0d',
+    'lavender blue': '#8b88f8',
+    'light grass green': '#9af764',
+    'light mint green': '#a6fbb2',
+    'sunflower': '#ffc512',
+    'velvet': '#750851',
+    'brick orange': '#c14a09',
+    'lightish red': '#fe2f4a',
+    'pure blue': '#0203e2',
+    'twilight blue': '#0a437a',
+    'violet red': '#a50055',
+    'yellowy brown': '#ae8b0c',
+    'carnation': '#fd798f',
+    'muddy yellow': '#bfac05',
+    'dark seafoam green': '#3eaf76',
+    'deep rose': '#c74767',
+    'dusty red': '#b9484e',
+    'grey/blue': '#647d8e',
+    'lemon lime': '#bffe28',
+    'purple/pink': '#d725de',
+    'brown yellow': '#b29705',
+    'purple brown': '#673a3f',
+    'wisteria': '#a87dc2',
+    'banana yellow': '#fafe4b',
+    'lipstick red': '#c0022f',
+    'water blue': '#0e87cc',
+    'brown grey': '#8d8468',
+    'vibrant purple': '#ad03de',
+    'baby green': '#8cff9e',
+    'barf green': '#94ac02',
+    'eggshell blue': '#c4fff7',
+    'sandy yellow': '#fdee73',
+    'cool green': '#33b864',
+    'pale': '#fff9d0',
+    'blue/grey': '#758da3',
+    'hot magenta': '#f504c9',
+    'greyblue': '#77a1b5',
+    'purpley': '#8756e4',
+    'baby shit green': '#889717',
+    'brownish pink': '#c27e79',
+    'dark aquamarine': '#017371',
+    'diarrhea': '#9f8303',
+    'light mustard': '#f7d560',
+    'pale sky blue': '#bdf6fe',
+    'turtle green': '#75b84f',
+    'bright olive': '#9cbb04',
+    'dark grey blue': '#29465b',
+    'greeny brown': '#696006',
+    'lemon green': '#adf802',
+    'light periwinkle': '#c1c6fc',
+    'seaweed green': '#35ad6b',
+    'sunshine yellow': '#fffd37',
+    'ugly purple': '#a442a0',
+    'medium pink': '#f36196',
+    'puke brown': '#947706',
+    'very light pink': '#fff4f2',
+    'viridian': '#1e9167',
+    'bile': '#b5c306',
+    'faded yellow': '#feff7f',
+    'very pale green': '#cffdbc',
+    'vibrant green': '#0add08',
+    'bright lime': '#87fd05',
+    'spearmint': '#1ef876',
+    'light aquamarine': '#7bfdc7',
+    'light sage': '#bcecac',
+    'yellowgreen': '#bbf90f',
+    'baby poo': '#ab9004',
+    'dark seafoam': '#1fb57a',
+    'deep teal': '#00555a',
+    'heather': '#a484ac',
+    'rust orange': '#c45508',
+    'dirty blue': '#3f829d',
+    'fern green': '#548d44',
+    'bright lilac': '#c95efb',
+    'weird green': '#3ae57f',
+    'peacock blue': '#016795',
+    'avocado green': '#87a922',
+    'faded orange': '#f0944d',
+    'grape purple': '#5d1451',
+    'hot green': '#25ff29',
+    'lime yellow': '#d0fe1d',
+    'mango': '#ffa62b',
+    'shamrock': '#01b44c',
+    'bubblegum': '#ff6cb5',
+    'purplish brown': '#6b4247',
+    'vomit yellow': '#c7c10c',
+    'pale cyan': '#b7fffa',
+    'key lime': '#aeff6e',
+    'tomato red': '#ec2d01',
+    'lightgreen': '#76ff7b',
+    'merlot': '#730039',
+    'night blue': '#040348',
+    'purpleish pink': '#df4ec8',
+    'apple': '#6ecb3c',
+    'baby poop green': '#8f9805',
+    'green apple': '#5edc1f',
+    'heliotrope': '#d94ff5',
+    'yellow/green': '#c8fd3d',
+    'almost black': '#070d0d',
+    'cool blue': '#4984b8',
+    'leafy green': '#51b73b',
+    'mustard brown': '#ac7e04',
+    'dusk': '#4e5481',
+    'dull brown': '#876e4b',
+    'frog green': '#58bc08',
+    'vivid green': '#2fef10',
+    'bright light green': '#2dfe54',
+    'fluro green': '#0aff02',
+    'kiwi': '#9cef43',
+    'seaweed': '#18d17b',
+    'navy green': '#35530a',
+    'ultramarine blue': '#1805db',
+    'iris': '#6258c4',
+    'pastel orange': '#ff964f',
+    'yellowish orange': '#ffab0f',
+    'perrywinkle': '#8f8ce7',
+    'tealish': '#24bca8',
+    'dark plum': '#3f012c',
+    'pear': '#cbf85f',
+    'pinkish orange': '#ff724c',
+    'midnight purple': '#280137',
+    'light urple': '#b36ff6',
+    'dark mint': '#48c072',
+    'greenish tan': '#bccb7a',
+    'light burgundy': '#a8415b',
+    'turquoise blue': '#06b1c4',
+    'ugly pink': '#cd7584',
+    'sandy': '#f1da7a',
+    'electric pink': '#ff0490',
+    'muted purple': '#805b87',
+    'mid green': '#50a747',
+    'greyish': '#a8a495',
+    'neon yellow': '#cfff04',
+    'banana': '#ffff7e',
+    'carnation pink': '#ff7fa7',
+    'tomato': '#ef4026',
+    'sea': '#3c9992',
+    'muddy brown': '#886806',
+    'turquoise green': '#04f489',
+    'buff': '#fef69e',
+    'fawn': '#cfaf7b',
+    'muted blue': '#3b719f',
+    'pale rose': '#fdc1c5',
+    'dark mint green': '#20c073',
+    'amethyst': '#9b5fc0',
+    'blue/green': '#0f9b8e',
+    'chestnut': '#742802',
+    'sick green': '#9db92c',
+    'pea': '#a4bf20',
+    'rusty orange': '#cd5909',
+    'stone': '#ada587',
+    'rose red': '#be013c',
+    'pale aqua': '#b8ffeb',
+    'deep orange': '#dc4d01',
+    'earth': '#a2653e',
+    'mossy green': '#638b27',
+    'grassy green': '#419c03',
+    'pale lime green': '#b1ff65',
+    'light grey blue': '#9dbcd4',
+    'pale grey': '#fdfdfe',
+    'asparagus': '#77ab56',
+    'blueberry': '#464196',
+    'purple red': '#990147',
+    'pale lime': '#befd73',
+    'greenish teal': '#32bf84',
+    'caramel': '#af6f09',
+    'deep magenta': '#a0025c',
+    'light peach': '#ffd8b1',
+    'milk chocolate': '#7f4e1e',
+    'ocher': '#bf9b0c',
+    'off green': '#6ba353',
+    'purply pink': '#f075e6',
+    'lightblue': '#7bc8f6',
+    'dusky blue': '#475f94',
+    'golden': '#f5bf03',
+    'light beige': '#fffeb6',
+    'butter yellow': '#fffd74',
+    'dusky purple': '#895b7b',
+    'french blue': '#436bad',
+    'ugly yellow': '#d0c101',
+    'greeny yellow': '#c6f808',
+    'orangish red': '#f43605',
+    'shamrock green': '#02c14d',
+    'orangish brown': '#b25f03',
+    'tree green': '#2a7e19',
+    'deep violet': '#490648',
+    'gunmetal': '#536267',
+    'blue/purple': '#5a06ef',
+    'cherry': '#cf0234',
+    'sandy brown': '#c4a661',
+    'warm grey': '#978a84',
+    'dark indigo': '#1f0954',
+    'midnight': '#03012d',
+    'bluey green': '#2bb179',
+    'grey pink': '#c3909b',
+    'soft purple': '#a66fb5',
+    'blood': '#770001',
+    'brown red': '#922b05',
+    'medium grey': '#7d7f7c',
+    'berry': '#990f4b',
+    'poo': '#8f7303',
+    'purpley pink': '#c83cb9',
+    'light salmon': '#fea993',
+    'snot': '#acbb0d',
+    'easter purple': '#c071fe',
+    'light yellow green': '#ccfd7f',
+    'dark navy blue': '#00022e',
+    'drab': '#828344',
+    'light rose': '#ffc5cb',
+    'rouge': '#ab1239',
+    'purplish red': '#b0054b',
+    'slime green': '#99cc04',
+    'baby poop': '#937c00',
+    'irish green': '#019529',
+    'pink/purple': '#ef1de7',
+    'dark navy': '#000435',
+    'greeny blue': '#42b395',
+    'light plum': '#9d5783',
+    'pinkish grey': '#c8aca9',
+    'dirty orange': '#c87606',
+    'rust red': '#aa2704',
+    'pale lilac': '#e4cbff',
+    'orangey red': '#fa4224',
+    'primary blue': '#0804f9',
+    'kermit green': '#5cb200',
+    'brownish purple': '#76424e',
+    'murky green': '#6c7a0e',
+    'wheat': '#fbdd7e',
+    'very dark purple': '#2a0134',
+    'bottle green': '#044a05',
+    'watermelon': '#fd4659',
+    'deep sky blue': '#0d75f8',
+    'fire engine red': '#fe0002',
+    'yellow ochre': '#cb9d06',
+    'pumpkin orange': '#fb7d07',
+    'pale olive': '#b9cc81',
+    'light lilac': '#edc8ff',
+    'lightish green': '#61e160',
+    'carolina blue': '#8ab8fe',
+    'mulberry': '#920a4e',
+    'shocking pink': '#fe02a2',
+    'auburn': '#9a3001',
+    'bright lime green': '#65fe08',
+    'celadon': '#befdb7',
+    'pinkish brown': '#b17261',
+    'poo brown': '#885f01',
+    'bright sky blue': '#02ccfe',
+    'celery': '#c1fd95',
+    'dirt brown': '#836539',
+    'strawberry': '#fb2943',
+    'dark lime': '#84b701',
+    'copper': '#b66325',
+    'medium brown': '#7f5112',
+    'muted green': '#5fa052',
+    "robin's egg": '#6dedfd',
+    'bright aqua': '#0bf9ea',
+    'bright lavender': '#c760ff',
+    'ivory': '#ffffcb',
+    'very light purple': '#f6cefc',
+    'light navy': '#155084',
+    'pink red': '#f5054f',
+    'olive brown': '#645403',
+    'poop brown': '#7a5901',
+    'mustard green': '#a8b504',
+    'ocean green': '#3d9973',
+    'very dark blue': '#000133',
+    'dusty green': '#76a973',
+    'light navy blue': '#2e5a88',
+    'minty green': '#0bf77d',
+    'adobe': '#bd6c48',
+    'barney': '#ac1db8',
+    'jade green': '#2baf6a',
+    'bright light blue': '#26f7fd',
+    'light lime': '#aefd6c',
+    'dark khaki': '#9b8f55',
+    'orange yellow': '#ffad01',
+    'ocre': '#c69c04',
+    'maize': '#f4d054',
+    'faded pink': '#de9dac',
+    'british racing green': '#05480d',
+    'sandstone': '#c9ae74',
+    'mud brown': '#60460f',
+    'light sea green': '#98f6b0',
+    'robin egg blue': '#8af1fe',
+    'aqua marine': '#2ee8bb',
+    'dark sea green': '#11875d',
+    'soft pink': '#fdb0c0',
+    'orangey brown': '#b16002',
+    'cherry red': '#f7022a',
+    'burnt yellow': '#d5ab09',
+    'brownish grey': '#86775f',
+    'camel': '#c69f59',
+    'purplish grey': '#7a687f',
+    'marine': '#042e60',
+    'greyish pink': '#c88d94',
+    'pale turquoise': '#a5fbd5',
+    'pastel yellow': '#fffe71',
+    'bluey purple': '#6241c7',
+    'canary yellow': '#fffe40',
+    'faded red': '#d3494e',
+    'sepia': '#985e2b',
+    'coffee': '#a6814c',
+    'bright magenta': '#ff08e8',
+    'mocha': '#9d7651',
+    'ecru': '#feffca',
+    'purpleish': '#98568d',
+    'cranberry': '#9e003a',
+    'darkish green': '#287c37',
+    'brown orange': '#b96902',
+    'dusky rose': '#ba6873',
+    'melon': '#ff7855',
+    'sickly green': '#94b21c',
+    'silver': '#c5c9c7',
+    'purply blue': '#661aee',
+    'purpleish blue': '#6140ef',
+    'hospital green': '#9be5aa',
+    'shit brown': '#7b5804',
+    'mid blue': '#276ab3',
+    'amber': '#feb308',
+    'easter green': '#8cfd7e',
+    'soft blue': '#6488ea',
+    'cerulean blue': '#056eee',
+    'golden brown': '#b27a01',
+    'bright turquoise': '#0ffef9',
+    'red pink': '#fa2a55',
+    'red purple': '#820747',
+    'greyish brown': '#7a6a4f',
+    'vermillion': '#f4320c',
+    'russet': '#a13905',
+    'steel grey': '#6f828a',
+    'lighter purple': '#a55af4',
+    'bright violet': '#ad0afd',
+    'prussian blue': '#004577',
+    'slate green': '#658d6d',
+    'dirty pink': '#ca7b80',
+    'dark blue green': '#005249',
+    'pine': '#2b5d34',
+    'yellowy green': '#bff128',
+    'dark gold': '#b59410',
+    'bluish': '#2976bb',
+    'darkish blue': '#014182',
+    'dull red': '#bb3f3f',
+    'pinky red': '#fc2647',
+    'bronze': '#a87900',
+    'pale teal': '#82cbb2',
+    'military green': '#667c3e',
+    'barbie pink': '#fe46a5',
+    'bubblegum pink': '#fe83cc',
+    'pea soup green': '#94a617',
+    'dark mustard': '#a88905',
+    'shit': '#7f5f00',
+    'medium purple': '#9e43a2',
+    'very dark green': '#062e03',
+    'dirt': '#8a6e45',
+    'dusky pink': '#cc7a8b',
+    'red violet': '#9e0168',
+    'lemon yellow': '#fdff38',
+    'pistachio': '#c0fa8b',
+    'dull yellow': '#eedc5b',
+    'dark lime green': '#7ebd01',
+    'denim blue': '#3b5b92',
+    'teal blue': '#01889f',
+    'lightish blue': '#3d7afd',
+    'purpley blue': '#5f34e7',
+    'light indigo': '#6d5acf',
+    'swamp green': '#748500',
+    'brown green': '#706c11',
+    'dark maroon': '#3c0008',
+    'hot purple': '#cb00f5',
+    'dark forest green': '#002d04',
+    'faded blue': '#658cbb',
+    'drab green': '#749551',
+    'light lime green': '#b9ff66',
+    'snot green': '#9dc100',
+    'yellowish': '#faee66',
+    'light blue green': '#7efbb3',
+    'bordeaux': '#7b002c',
+    'light mauve': '#c292a1',
+    'ocean': '#017b92',
+    'marigold': '#fcc006',
+    'muddy green': '#657432',
+    'dull orange': '#d8863b',
+    'steel': '#738595',
+    'electric purple': '#aa23ff',
+    'fluorescent green': '#08ff08',
+    'yellowish brown': '#9b7a01',
+    'blush': '#f29e8e',
+    'soft green': '#6fc276',
+    'bright orange': '#ff5b00',
+    'lemon': '#fdff52',
+    'purple grey': '#866f85',
+    'acid green': '#8ffe09',
+    'pale lavender': '#eecffe',
+    'violet blue': '#510ac9',
+    'light forest green': '#4f9153',
+    'burnt red': '#9f2305',
+    'khaki green': '#728639',
+    'cerise': '#de0c62',
+    'faded purple': '#916e99',
+    'apricot': '#ffb16d',
+    'dark olive green': '#3c4d03',
+    'grey brown': '#7f7053',
+    'green grey': '#77926f',
+    'true blue': '#010fcc',
+    'pale violet': '#ceaefa',
+    'periwinkle blue': '#8f99fb',
+    'light sky blue': '#c6fcff',
+    'blurple': '#5539cc',
+    'green brown': '#544e03',
+    'bluegreen': '#017a79',
+    'bright teal': '#01f9c6',
+    'brownish yellow': '#c9b003',
+    'pea soup': '#929901',
+    'forest': '#0b5509',
+    'barney purple': '#a00498',
+    'ultramarine': '#2000b1',
+    'purplish': '#94568c',
+    'puke yellow': '#c2be0e',
+    'bluish grey': '#748b97',
+    'dark periwinkle': '#665fd1',
+    'dark lilac': '#9c6da5',
+    'reddish': '#c44240',
+    'light maroon': '#a24857',
+    'dusty purple': '#825f87',
+    'terra cotta': '#c9643b',
+    'avocado': '#90b134',
+    'marine blue': '#01386a',
+    'teal green': '#25a36f',
+    'slate grey': '#59656d',
+    'lighter green': '#75fd63',
+    'electric green': '#21fc0d',
+    'dusty blue': '#5a86ad',
+    'golden yellow': '#fec615',
+    'bright yellow': '#fffd01',
+    'light lavender': '#dfc5fe',
+    'umber': '#b26400',
+    'poop': '#7f5e00',
+    'dark peach': '#de7e5d',
+    'jungle green': '#048243',
+    'eggshell': '#ffffd4',
+    'denim': '#3b638c',
+    'yellow brown': '#b79400',
+    'dull purple': '#84597e',
+    'chocolate brown': '#411900',
+    'wine red': '#7b0323',
+    'neon blue': '#04d9ff',
+    'dirty green': '#667e2c',
+    'light tan': '#fbeeac',
+    'ice blue': '#d7fffe',
+    'cadet blue': '#4e7496',
+    'dark mauve': '#874c62',
+    'very light blue': '#d5ffff',
+    'grey purple': '#826d8c',
+    'pastel pink': '#ffbacd',
+    'very light green': '#d1ffbd',
+    'dark sky blue': '#448ee4',
+    'evergreen': '#05472a',
+    'dull pink': '#d5869d',
+    'aubergine': '#3d0734',
+    'mahogany': '#4a0100',
+    'reddish orange': '#f8481c',
+    'deep green': '#02590f',
+    'vomit green': '#89a203',
+    'purple pink': '#e03fd8',
+    'dusty pink': '#d58a94',
+    'faded green': '#7bb274',
+    'camo green': '#526525',
+    'pinky purple': '#c94cbe',
+    'pink purple': '#db4bda',
+    'brownish red': '#9e3623',
+    'dark rose': '#b5485d',
+    'mud': '#735c12',
+    'brownish': '#9c6d57',
+    'emerald green': '#028f1e',
+    'pale brown': '#b1916e',
+    'dull blue': '#49759c',
+    'burnt umber': '#a0450e',
+    'medium green': '#39ad48',
+    'clay': '#b66a50',
+    'light aqua': '#8cffdb',
+    'light olive green': '#a4be5c',
+    'brownish orange': '#cb7723',
+    'dark aqua': '#05696b',
+    'purplish pink': '#ce5dae',
+    'dark salmon': '#c85a53',
+    'greenish grey': '#96ae8d',
+    'jade': '#1fa774',
+    'ugly green': '#7a9703',
+    'dark beige': '#ac9362',
+    'emerald': '#01a049',
+    'pale red': '#d9544d',
+    'light magenta': '#fa5ff7',
+    'sky': '#82cafc',
+    'light cyan': '#acfffc',
+    'yellow orange': '#fcb001',
+    'reddish purple': '#910951',
+    'reddish pink': '#fe2c54',
+    'orchid': '#c875c4',
+    'dirty yellow': '#cdc50a',
+    'orange red': '#fd411e',
+    'deep red': '#9a0200',
+    'orange brown': '#be6400',
+    'cobalt blue': '#030aa7',
+    'neon pink': '#fe019a',
+    'rose pink': '#f7879a',
+    'greyish purple': '#887191',
+    'raspberry': '#b00149',
+    'aqua green': '#12e193',
+    'salmon pink': '#fe7b7c',
+    'tangerine': '#ff9408',
+    'brownish green': '#6a6e09',
+    'red brown': '#8b2e16',
+    'greenish brown': '#696112',
+    'pumpkin': '#e17701',
+    'pine green': '#0a481e',
+    'charcoal': '#343837',
+    'baby pink': '#ffb7ce',
+    'cornflower': '#6a79f7',
+    'blue violet': '#5d06e9',
+    'chocolate': '#3d1c02',
+    'greyish green': '#82a67d',
+    'scarlet': '#be0119',
+    'green yellow': '#c9ff27',
+    'dark olive': '#373e02',
+    'sienna': '#a9561e',
+    'pastel purple': '#caa0ff',
+    'terracotta': '#ca6641',
+    'aqua blue': '#02d8e9',
+    'sage green': '#88b378',
+    'blood red': '#980002',
+    'deep pink': '#cb0162',
+    'grass': '#5cac2d',
+    'moss': '#769958',
+    'pastel blue': '#a2bffe',
+    'bluish green': '#10a674',
+    'green blue': '#06b48b',
+    'dark tan': '#af884a',
+    'greenish blue': '#0b8b87',
+    'pale orange': '#ffa756',
+    'vomit': '#a2a415',
+    'forrest green': '#154406',
+    'dark lavender': '#856798',
+    'dark violet': '#34013f',
+    'purple blue': '#632de9',
+    'dark cyan': '#0a888a',
+    'olive drab': '#6f7632',
+    'pinkish': '#d46a7e',
+    'cobalt': '#1e488f',
+    'neon purple': '#bc13fe',
+    'light turquoise': '#7ef4cc',
+    'apple green': '#76cd26',
+    'dull green': '#74a662',
+    'wine': '#80013f',
+    'powder blue': '#b1d1fc',
+    'off white': '#ffffe4',
+    'electric blue': '#0652ff',
+    'dark turquoise': '#045c5a',
+    'blue purple': '#5729ce',
+    'azure': '#069af3',
+    'bright red': '#ff000d',
+    'pinkish red': '#f10c45',
+    'cornflower blue': '#5170d7',
+    'light olive': '#acbf69',
+    'grape': '#6c3461',
+    'greyish blue': '#5e819d',
+    'purplish blue': '#601ef9',
+    'yellowish green': '#b0dd16',
+    'greenish yellow': '#cdfd02',
+    'medium blue': '#2c6fbb',
+    'dusty rose': '#c0737a',
+    'light violet': '#d6b4fc',
+    'midnight blue': '#020035',
+    'bluish purple': '#703be7',
+    'red orange': '#fd3c06',
+    'dark magenta': '#960056',
+    'greenish': '#40a368',
+    'ocean blue': '#03719c',
+    'coral': '#fc5a50',
+    'cream': '#ffffc2',
+    'reddish brown': '#7f2b0a',
+    'burnt sienna': '#b04e0f',
+    'brick': '#a03623',
+    'sage': '#87ae73',
+    'grey green': '#789b73',
+    'white': '#ffffff',
+    "robin's egg blue": '#98eff9',
+    'moss green': '#658b38',
+    'steel blue': '#5a7d9a',
+    'eggplant': '#380835',
+    'light yellow': '#fffe7a',
+    'leaf green': '#5ca904',
+    'light grey': '#d8dcd6',
+    'puke': '#a5a502',
+    'pinkish purple': '#d648d7',
+    'sea blue': '#047495',
+    'pale purple': '#b790d4',
+    'slate blue': '#5b7c99',
+    'blue grey': '#607c8e',
+    'hunter green': '#0b4008',
+    'fuchsia': '#ed0dd9',
+    'crimson': '#8c000f',
+    'pale yellow': '#ffff84',
+    'ochre': '#bf9005',
+    'mustard yellow': '#d2bd0a',
+    'light red': '#ff474c',
+    'cerulean': '#0485d1',
+    'pale pink': '#ffcfdc',
+    'deep blue': '#040273',
+    'rust': '#a83c09',
+    'light teal': '#90e4c1',
+    'slate': '#516572',
+    'goldenrod': '#fac205',
+    'dark yellow': '#d5b60a',
+    'dark grey': '#363737',
+    'army green': '#4b5d16',
+    'grey blue': '#6b8ba4',
+    'seafoam': '#80f9ad',
+    'puce': '#a57e52',
+    'spring green': '#a9f971',
+    'dark orange': '#c65102',
+    'sand': '#e2ca76',
+    'pastel green': '#b0ff9d',
+    'mint': '#9ffeb0',
+    'light orange': '#fdaa48',
+    'bright pink': '#fe01b1',
+    'chartreuse': '#c1f80a',
+    'deep purple': '#36013f',
+    'dark brown': '#341c02',
+    'taupe': '#b9a281',
+    'pea green': '#8eab12',
+    'puke green': '#9aae07',
+    'kelly green': '#02ab2e',
+    'seafoam green': '#7af9ab',
+    'blue green': '#137e6d',
+    'khaki': '#aaa662',
+    'burgundy': '#610023',
+    'dark teal': '#014d4e',
+    'brick red': '#8f1402',
+    'royal purple': '#4b006e',
+    'plum': '#580f41',
+    'mint green': '#8fff9f',
+    'gold': '#dbb40c',
+    'baby blue': '#a2cffe',
+    'yellow green': '#c0fb2d',
+    'bright purple': '#be03fd',
+    'dark red': '#840000',
+    'pale blue': '#d0fefe',
+    'grass green': '#3f9b0b',
+    'navy': '#01153e',
+    'aquamarine': '#04d8b2',
+    'burnt orange': '#c04e01',
+    'neon green': '#0cff0c',
+    'bright blue': '#0165fc',
+    'rose': '#cf6275',
+    'light pink': '#ffd1df',
+    'mustard': '#ceb301',
+    'indigo': '#380282',
+    'lime': '#aaff32',
+    'sea green': '#53fca1',
+    'periwinkle': '#8e82fe',
+    'dark pink': '#cb416b',
+    'olive green': '#677a04',
+    'peach': '#ffb07c',
+    'pale green': '#c7fdb5',
+    'light brown': '#ad8150',
+    'hot pink': '#ff028d',
+    'black': '#000000',
+    'lilac': '#cea2fd',
+    'navy blue': '#001146',
+    'royal blue': '#0504aa',
+    'beige': '#e6daa6',
+    'salmon': '#ff796c',
+    'olive': '#6e750e',
+    'maroon': '#650021',
+    'bright green': '#01ff07',
+    'dark purple': '#35063e',
+    'mauve': '#ae7181',
+    'forest green': '#06470c',
+    'aqua': '#13eac9',
+    'cyan': '#00ffff',
+    'tan': '#d1b26f',
+    'dark blue': '#00035b',
+    'lavender': '#c79fef',
+    'turquoise': '#06c2ac',
+    'dark green': '#033500',
+    'violet': '#9a0eea',
+    'light purple': '#bf77f6',
+    'lime green': '#89fe05',
+    'grey': '#929591',
+    'sky blue': '#75bbfd',
+    'yellow': '#ffff14',
+    'magenta': '#c20078',
+    'light green': '#96f97b',
+    'orange': '#f97306',
+    'teal': '#029386',
+    'light blue': '#95d0fc',
+    'red': '#e50000',
+    'brown': '#653700',
+    'pink': '#ff81c0',
+    'blue': '#0343df',
+    'green': '#15b01a',
+    'purple': '#7e1e9c'}
+
+# Normalize name to "xkcd:<name>" to avoid name collisions.
+XKCD_COLORS = {'xkcd:' + name: value for name, value in XKCD_COLORS.items()}
+
+
+# https://drafts.csswg.org/css-color-4/#named-colors
+CSS4_COLORS = {
+    'aliceblue':            '#F0F8FF',
+    'antiquewhite':         '#FAEBD7',
+    'aqua':                 '#00FFFF',
+    'aquamarine':           '#7FFFD4',
+    'azure':                '#F0FFFF',
+    'beige':                '#F5F5DC',
+    'bisque':               '#FFE4C4',
+    'black':                '#000000',
+    'blanchedalmond':       '#FFEBCD',
+    'blue':                 '#0000FF',
+    'blueviolet':           '#8A2BE2',
+    'brown':                '#A52A2A',
+    'burlywood':            '#DEB887',
+    'cadetblue':            '#5F9EA0',
+    'chartreuse':           '#7FFF00',
+    'chocolate':            '#D2691E',
+    'coral':                '#FF7F50',
+    'cornflowerblue':       '#6495ED',
+    'cornsilk':             '#FFF8DC',
+    'crimson':              '#DC143C',
+    'cyan':                 '#00FFFF',
+    'darkblue':             '#00008B',
+    'darkcyan':             '#008B8B',
+    'darkgoldenrod':        '#B8860B',
+    'darkgray':             '#A9A9A9',
+    'darkgreen':            '#006400',
+    'darkgrey':             '#A9A9A9',
+    'darkkhaki':            '#BDB76B',
+    'darkmagenta':          '#8B008B',
+    'darkolivegreen':       '#556B2F',
+    'darkorange':           '#FF8C00',
+    'darkorchid':           '#9932CC',
+    'darkred':              '#8B0000',
+    'darksalmon':           '#E9967A',
+    'darkseagreen':         '#8FBC8F',
+    'darkslateblue':        '#483D8B',
+    'darkslategray':        '#2F4F4F',
+    'darkslategrey':        '#2F4F4F',
+    'darkturquoise':        '#00CED1',
+    'darkviolet':           '#9400D3',
+    'deeppink':             '#FF1493',
+    'deepskyblue':          '#00BFFF',
+    'dimgray':              '#696969',
+    'dimgrey':              '#696969',
+    'dodgerblue':           '#1E90FF',
+    'firebrick':            '#B22222',
+    'floralwhite':          '#FFFAF0',
+    'forestgreen':          '#228B22',
+    'fuchsia':              '#FF00FF',
+    'gainsboro':            '#DCDCDC',
+    'ghostwhite':           '#F8F8FF',
+    'gold':                 '#FFD700',
+    'goldenrod':            '#DAA520',
+    'gray':                 '#808080',
+    'green':                '#008000',
+    'greenyellow':          '#ADFF2F',
+    'grey':                 '#808080',
+    'honeydew':             '#F0FFF0',
+    'hotpink':              '#FF69B4',
+    'indianred':            '#CD5C5C',
+    'indigo':               '#4B0082',
+    'ivory':                '#FFFFF0',
+    'khaki':                '#F0E68C',
+    'lavender':             '#E6E6FA',
+    'lavenderblush':        '#FFF0F5',
+    'lawngreen':            '#7CFC00',
+    'lemonchiffon':         '#FFFACD',
+    'lightblue':            '#ADD8E6',
+    'lightcoral':           '#F08080',
+    'lightcyan':            '#E0FFFF',
+    'lightgoldenrodyellow': '#FAFAD2',
+    'lightgray':            '#D3D3D3',
+    'lightgreen':           '#90EE90',
+    'lightgrey':            '#D3D3D3',
+    'lightpink':            '#FFB6C1',
+    'lightsalmon':          '#FFA07A',
+    'lightseagreen':        '#20B2AA',
+    'lightskyblue':         '#87CEFA',
+    'lightslategray':       '#778899',
+    'lightslategrey':       '#778899',
+    'lightsteelblue':       '#B0C4DE',
+    'lightyellow':          '#FFFFE0',
+    'lime':                 '#00FF00',
+    'limegreen':            '#32CD32',
+    'linen':                '#FAF0E6',
+    'magenta':              '#FF00FF',
+    'maroon':               '#800000',
+    'mediumaquamarine':     '#66CDAA',
+    'mediumblue':           '#0000CD',
+    'mediumorchid':         '#BA55D3',
+    'mediumpurple':         '#9370DB',
+    'mediumseagreen':       '#3CB371',
+    'mediumslateblue':      '#7B68EE',
+    'mediumspringgreen':    '#00FA9A',
+    'mediumturquoise':      '#48D1CC',
+    'mediumvioletred':      '#C71585',
+    'midnightblue':         '#191970',
+    'mintcream':            '#F5FFFA',
+    'mistyrose':            '#FFE4E1',
+    'moccasin':             '#FFE4B5',
+    'navajowhite':          '#FFDEAD',
+    'navy':                 '#000080',
+    'oldlace':              '#FDF5E6',
+    'olive':                '#808000',
+    'olivedrab':            '#6B8E23',
+    'orange':               '#FFA500',
+    'orangered':            '#FF4500',
+    'orchid':               '#DA70D6',
+    'palegoldenrod':        '#EEE8AA',
+    'palegreen':            '#98FB98',
+    'paleturquoise':        '#AFEEEE',
+    'palevioletred':        '#DB7093',
+    'papayawhip':           '#FFEFD5',
+    'peachpuff':            '#FFDAB9',
+    'peru':                 '#CD853F',
+    'pink':                 '#FFC0CB',
+    'plum':                 '#DDA0DD',
+    'powderblue':           '#B0E0E6',
+    'purple':               '#800080',
+    'rebeccapurple':        '#663399',
+    'red':                  '#FF0000',
+    'rosybrown':            '#BC8F8F',
+    'royalblue':            '#4169E1',
+    'saddlebrown':          '#8B4513',
+    'salmon':               '#FA8072',
+    'sandybrown':           '#F4A460',
+    'seagreen':             '#2E8B57',
+    'seashell':             '#FFF5EE',
+    'sienna':               '#A0522D',
+    'silver':               '#C0C0C0',
+    'skyblue':              '#87CEEB',
+    'slateblue':            '#6A5ACD',
+    'slategray':            '#708090',
+    'slategrey':            '#708090',
+    'snow':                 '#FFFAFA',
+    'springgreen':          '#00FF7F',
+    'steelblue':            '#4682B4',
+    'tan':                  '#D2B48C',
+    'teal':                 '#008080',
+    'thistle':              '#D8BFD8',
+    'tomato':               '#FF6347',
+    'turquoise':            '#40E0D0',
+    'violet':               '#EE82EE',
+    'wheat':                '#F5DEB3',
+    'white':                '#FFFFFF',
+    'whitesmoke':           '#F5F5F5',
+    'yellow':               '#FFFF00',
+    'yellowgreen':          '#9ACD32'}

+ 726 - 0
venv/lib/python3.8/site-packages/matplotlib/_constrained_layout.py

@@ -0,0 +1,726 @@
+"""
+This module provides the routine to adjust subplot layouts so that there are
+no overlapping axes or axes decorations.  All axes decorations are dealt with
+(labels, ticks, titles, ticklabels) and some dependent artists are also dealt
+with (colorbar, suptitle, legend).
+
+Layout is done via :meth:`~matplotlib.gridspec`, with one constraint per
+gridspec, so it is possible to have overlapping axes if the gridspecs
+overlap (i.e. using :meth:`~matplotlib.gridspec.GridSpecFromSubplotSpec`).
+Axes placed using ``figure.subplots()`` or ``figure.add_subplots()`` will
+participate in the layout.  Axes manually placed via ``figure.add_axes()``
+will not.
+
+See Tutorial: :doc:`/tutorials/intermediate/constrainedlayout_guide`
+
+"""
+
+# Development Notes:
+
+# What gets a layoutbox:
+#  - figure
+#    - gridspec
+#      - subplotspec
+#        EITHER:
+#         - axes + pos for the axes (i.e. the total area taken by axis and
+#            the actual "position" argument that needs to be sent to
+#             ax.set_position.)
+#           - The axes layout box will also encompass the legend, and that is
+#             how legends get included (axes legends, not figure legends)
+#         - colorbars are siblings of the axes if they are single-axes
+#           colorbars
+#        OR:
+#         - a gridspec can be inside a subplotspec.
+#           - subplotspec
+#           EITHER:
+#            - axes...
+#           OR:
+#            - gridspec... with arbitrary nesting...
+#      - colorbars are siblings of the subplotspecs if they are multi-axes
+#        colorbars.
+#   - suptitle:
+#      - right now suptitles are just stacked atop everything else in figure.
+#        Could imagine suptitles being gridspec suptitles, but not implemented
+#
+#   Todo:    AnchoredOffsetbox connected to gridspecs or axes.  This would
+#        be more general way to add extra-axes annotations.
+
+import logging
+
+import numpy as np
+
+import matplotlib.cbook as cbook
+import matplotlib._layoutbox as layoutbox
+
+_log = logging.getLogger(__name__)
+
+
+def _in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax):
+    return (colnumCmin <= colnum0min <= colnumCmax
+            or colnumCmin <= colnum0max <= colnumCmax)
+
+
+def _in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax):
+    return (rownumCmin <= rownum0min <= rownumCmax
+            or rownumCmin <= rownum0max <= rownumCmax)
+
+
+def _axes_all_finite_sized(fig):
+    """Return whether all axes in the figure have a finite width and height."""
+    for ax in fig.axes:
+        if ax._layoutbox is not None:
+            newpos = ax._poslayoutbox.get_rect()
+            if newpos[2] <= 0 or newpos[3] <= 0:
+                return False
+    return True
+
+
+######################################################
+def do_constrained_layout(fig, renderer, h_pad, w_pad,
+        hspace=None, wspace=None):
+    """
+    Do the constrained_layout.  Called at draw time in
+     ``figure.constrained_layout()``
+
+    Parameters
+    ----------
+    fig : Figure
+      is the ``figure`` instance to do the layout in.
+
+    renderer : Renderer
+      the renderer to use.
+
+     h_pad, w_pad : float
+       are in figure-normalized units, and are a padding around the axes
+       elements.
+
+     hspace, wspace : float
+        are in fractions of the subplot sizes.
+
+    """
+
+    # Steps:
+    #
+    # 1. get a list of unique gridspecs in this figure.  Each gridspec will be
+    # constrained separately.
+    # 2. Check for gaps in the gridspecs.  i.e. if not every axes slot in the
+    # gridspec has been filled.  If empty, add a ghost axis that is made so
+    # that it cannot be seen (though visible=True).  This is needed to make
+    # a blank spot in the layout.
+    # 3. Compare the tight_bbox of each axes to its `position`, and assume that
+    # the difference is the space needed by the elements around the edge of
+    # the axes (decorations) like the title, ticklabels, x-labels, etc.  This
+    # can include legends who overspill the axes boundaries.
+    # 4. Constrain gridspec elements to line up:
+    #     a) if colnum0 != colnumC, the two subplotspecs are stacked next to
+    #     each other, with the appropriate order.
+    #     b) if colnum0 == colnumC, line up the left or right side of the
+    #     _poslayoutbox (depending if it is the min or max num that is equal).
+    #     c) do the same for rows...
+    # 5. The above doesn't constrain relative sizes of the _poslayoutboxes
+    # at all, and indeed zero-size is a solution that the solver often finds
+    # more convenient than expanding the sizes.  Right now the solution is to
+    # compare subplotspec sizes (i.e. drowsC and drows0) and constrain the
+    # larger _poslayoutbox to be larger than the ratio of the sizes. i.e. if
+    # drows0 > drowsC, then ax._poslayoutbox > axc._poslayoutbox*drowsC/drows0.
+    # This works fine *if* the decorations are similar between the axes.
+    # If the larger subplotspec has much larger axes decorations, then the
+    # constraint above is incorrect.
+    #
+    # We need the greater than in the above, in general, rather than an equals
+    # sign.  Consider the case of the left column having 2 rows, and the right
+    # column having 1 row.  We want the top and bottom of the _poslayoutboxes
+    # to line up. So that means if there are decorations on the left column
+    # axes they will be smaller than half as large as the right hand axis.
+    #
+    # This can break down if the decoration size for the right hand axis (the
+    # margins) is very large.  There must be a math way to check for this case.
+
+    invTransFig = fig.transFigure.inverted().transform_bbox
+
+    # list of unique gridspecs that contain child axes:
+    gss = set()
+    for ax in fig.axes:
+        if hasattr(ax, 'get_subplotspec'):
+            gs = ax.get_subplotspec().get_gridspec()
+            if gs._layoutbox is not None:
+                gss.add(gs)
+    if len(gss) == 0:
+        cbook._warn_external('There are no gridspecs with layoutboxes. '
+                             'Possibly did not call parent GridSpec with the'
+                             ' figure= keyword')
+
+    if fig._layoutbox.constrained_layout_called < 1:
+        for gs in gss:
+            # fill in any empty gridspec slots w/ ghost axes...
+            _make_ghost_gridspec_slots(fig, gs)
+
+    for nnn in range(2):
+        # do the algorithm twice.  This has to be done because decorators
+        # change size after the first re-position (i.e. x/yticklabels get
+        # larger/smaller).  This second reposition tends to be much milder,
+        # so doing twice makes things work OK.
+        for ax in fig.axes:
+            _log.debug(ax._layoutbox)
+            if ax._layoutbox is not None:
+                # make margins for each layout box based on the size of
+                # the decorators.
+                _make_layout_margins(ax, renderer, h_pad, w_pad)
+
+        # do layout for suptitle.
+        suptitle = fig._suptitle
+        do_suptitle = (suptitle is not None and
+                       suptitle._layoutbox is not None and
+                       suptitle.get_in_layout())
+        if do_suptitle:
+            bbox = invTransFig(
+                suptitle.get_window_extent(renderer=renderer))
+            height = bbox.y1 - bbox.y0
+            if np.isfinite(height):
+                # reserve at top of figure include an h_pad above and below
+                suptitle._layoutbox.edit_height(height + h_pad * 2)
+
+        # OK, the above lines up ax._poslayoutbox with ax._layoutbox
+        # now we need to
+        #   1) arrange the subplotspecs.  We do it at this level because
+        #      the subplotspecs are meant to contain other dependent axes
+        #      like colorbars or legends.
+        #   2) line up the right and left side of the ax._poslayoutbox
+        #      that have the same subplotspec maxes.
+
+        if fig._layoutbox.constrained_layout_called < 1:
+            # arrange the subplotspecs...  This is all done relative to each
+            # other.  Some subplotspecs contain axes, and others contain
+            # gridspecs the ones that contain gridspecs are a set proportion
+            # of their parent gridspec.  The ones that contain axes are
+            # not so constrained.
+            figlb = fig._layoutbox
+            for child in figlb.children:
+                if child._is_gridspec_layoutbox():
+                    # This routine makes all the subplot spec containers
+                    # have the correct arrangement.  It just stacks the
+                    # subplot layoutboxes in the correct order...
+                    _arrange_subplotspecs(child, hspace=hspace, wspace=wspace)
+
+            for gs in gss:
+                _align_spines(fig, gs)
+
+        fig._layoutbox.constrained_layout_called += 1
+        fig._layoutbox.update_variables()
+
+        # check if any axes collapsed to zero.  If not, don't change positions:
+        if _axes_all_finite_sized(fig):
+            # Now set the position of the axes...
+            for ax in fig.axes:
+                if ax._layoutbox is not None:
+                    newpos = ax._poslayoutbox.get_rect()
+                    # Now set the new position.
+                    # ax.set_position will zero out the layout for
+                    # this axis, allowing users to hard-code the position,
+                    # so this does the same w/o zeroing layout.
+                    ax._set_position(newpos, which='original')
+            if do_suptitle:
+                newpos = suptitle._layoutbox.get_rect()
+                suptitle.set_y(1.0 - h_pad)
+            else:
+                if suptitle is not None and suptitle._layoutbox is not None:
+                    suptitle._layoutbox.edit_height(0)
+        else:
+            cbook._warn_external('constrained_layout not applied.  At least '
+                                 'one axes collapsed to zero width or height.')
+
+
+def _make_ghost_gridspec_slots(fig, gs):
+    """
+    Check for unoccupied gridspec slots and make ghost axes for these
+    slots...  Do for each gs separately.  This is a pretty big kludge
+    but shouldn't have too much ill effect.  The worst is that
+    someone querying the figure will wonder why there are more
+    axes than they thought.
+    """
+    nrows, ncols = gs.get_geometry()
+    hassubplotspec = np.zeros(nrows * ncols, dtype=bool)
+    axs = []
+    for ax in fig.axes:
+        if (hasattr(ax, 'get_subplotspec')
+                and ax._layoutbox is not None
+                and ax.get_subplotspec().get_gridspec() == gs):
+            axs += [ax]
+    for ax in axs:
+        ss0 = ax.get_subplotspec()
+        hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True
+    for nn, hss in enumerate(hassubplotspec):
+        if not hss:
+            # this gridspec slot doesn't have an axis so we
+            # make a "ghost".
+            ax = fig.add_subplot(gs[nn])
+            ax.set_visible(False)
+
+
+def _make_layout_margins(ax, renderer, h_pad, w_pad):
+    """
+    For each axes, make a margin between the *pos* layoutbox and the
+    *axes* layoutbox be a minimum size that can accommodate the
+    decorations on the axis.
+    """
+    fig = ax.figure
+    invTransFig = fig.transFigure.inverted().transform_bbox
+    pos = ax.get_position(original=True)
+    tightbbox = ax.get_tightbbox(renderer=renderer)
+    if tightbbox is None:
+        bbox = pos
+    else:
+        bbox = invTransFig(tightbbox)
+
+    # this can go wrong:
+    if not (np.isfinite(bbox.width) and np.isfinite(bbox.height)):
+        # just abort, this is likely a bad set of co-ordinates that
+        # is transitory...
+        return
+    # use stored h_pad if it exists
+    h_padt = ax._poslayoutbox.h_pad
+    if h_padt is None:
+        h_padt = h_pad
+    w_padt = ax._poslayoutbox.w_pad
+    if w_padt is None:
+        w_padt = w_pad
+    ax._poslayoutbox.edit_left_margin_min(-bbox.x0 +
+            pos.x0 + w_padt)
+    ax._poslayoutbox.edit_right_margin_min(bbox.x1 -
+            pos.x1 + w_padt)
+    ax._poslayoutbox.edit_bottom_margin_min(
+            -bbox.y0 + pos.y0 + h_padt)
+    ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt)
+    _log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad))
+    _log.debug('right %f', (bbox.x1 - pos.x1 + w_pad))
+    _log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt))
+    _log.debug('bbox.y0 %f', bbox.y0)
+    _log.debug('pos.y0 %f', pos.y0)
+    # Sometimes its possible for the solver to collapse
+    # rather than expand axes, so they all have zero height
+    # or width.  This stops that...  It *should* have been
+    # taken into account w/ pref_width...
+    if fig._layoutbox.constrained_layout_called < 1:
+        ax._poslayoutbox.constrain_height_min(20, strength='weak')
+        ax._poslayoutbox.constrain_width_min(20, strength='weak')
+        ax._layoutbox.constrain_height_min(20, strength='weak')
+        ax._layoutbox.constrain_width_min(20, strength='weak')
+        ax._poslayoutbox.constrain_top_margin(0, strength='weak')
+        ax._poslayoutbox.constrain_bottom_margin(0,
+                strength='weak')
+        ax._poslayoutbox.constrain_right_margin(0, strength='weak')
+        ax._poslayoutbox.constrain_left_margin(0, strength='weak')
+
+
+def _align_spines(fig, gs):
+    """
+    - Align right/left and bottom/top spines of appropriate subplots.
+    - Compare size of subplotspec including height and width ratios
+       and make sure that the axes spines are at least as large
+       as they should be.
+    """
+    # for each gridspec...
+    nrows, ncols = gs.get_geometry()
+    width_ratios = gs.get_width_ratios()
+    height_ratios = gs.get_height_ratios()
+    if width_ratios is None:
+        width_ratios = np.ones(ncols)
+    if height_ratios is None:
+        height_ratios = np.ones(nrows)
+
+    # get axes in this gridspec....
+    axs = []
+    for ax in fig.axes:
+        if (hasattr(ax, 'get_subplotspec')
+                and ax._layoutbox is not None):
+            if ax.get_subplotspec().get_gridspec() == gs:
+                axs += [ax]
+    rownummin = np.zeros(len(axs), dtype=np.int8)
+    rownummax = np.zeros(len(axs), dtype=np.int8)
+    colnummin = np.zeros(len(axs), dtype=np.int8)
+    colnummax = np.zeros(len(axs), dtype=np.int8)
+    width = np.zeros(len(axs))
+    height = np.zeros(len(axs))
+
+    for n, ax in enumerate(axs):
+        ss0 = ax.get_subplotspec()
+        rownummin[n], colnummin[n] = divmod(ss0.num1, ncols)
+        rownummax[n], colnummax[n] = divmod(ss0.num2, ncols)
+        width[n] = np.sum(
+                width_ratios[colnummin[n]:(colnummax[n] + 1)])
+        height[n] = np.sum(
+                height_ratios[rownummin[n]:(rownummax[n] + 1)])
+
+    for nn, ax in enumerate(axs[:-1]):
+        # now compare ax to all the axs:
+        #
+        # If the subplotspecs have the same colnumXmax, then line
+        # up their right sides.  If they have the same min, then
+        # line up their left sides (and vertical equivalents).
+        rownum0min, colnum0min = rownummin[nn], colnummin[nn]
+        rownum0max, colnum0max = rownummax[nn], colnummax[nn]
+        width0, height0 = width[nn], height[nn]
+        alignleft = False
+        alignright = False
+        alignbot = False
+        aligntop = False
+        alignheight = False
+        alignwidth = False
+        for mm in range(nn+1, len(axs)):
+            axc = axs[mm]
+            rownumCmin, colnumCmin = rownummin[mm], colnummin[mm]
+            rownumCmax, colnumCmax = rownummax[mm], colnummax[mm]
+            widthC, heightC = width[mm], height[mm]
+            # Horizontally align axes spines if they have the
+            # same min or max:
+            if not alignleft and colnum0min == colnumCmin:
+                # we want the _poslayoutboxes to line up on left
+                # side of the axes spines...
+                layoutbox.align([ax._poslayoutbox,
+                                 axc._poslayoutbox],
+                                'left')
+                alignleft = True
+
+            if not alignright and colnum0max == colnumCmax:
+                # line up right sides of _poslayoutbox
+                layoutbox.align([ax._poslayoutbox,
+                                 axc._poslayoutbox],
+                                'right')
+                alignright = True
+            # Vertically align axes spines if they have the
+            # same min or max:
+            if not aligntop and rownum0min == rownumCmin:
+                # line up top of _poslayoutbox
+                _log.debug('rownum0min == rownumCmin')
+                layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
+                                'top')
+                aligntop = True
+
+            if not alignbot and rownum0max == rownumCmax:
+                # line up bottom of _poslayoutbox
+                _log.debug('rownum0max == rownumCmax')
+                layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
+                                'bottom')
+                alignbot = True
+            ###########
+            # Now we make the widths and heights of position boxes
+            # similar. (i.e the spine locations)
+            # This allows vertically stacked subplots to have
+            # different sizes if they occupy different amounts
+            # of the gridspec:  i.e.
+            # gs = gridspec.GridSpec(3, 1)
+            # ax1 = gs[0,:]
+            # ax2 = gs[1:,:]
+            # then drows0 = 1, and drowsC = 2, and ax2
+            # should be at least twice as large as ax1.
+            # But it can be more than twice as large because
+            # it needs less room for the labeling.
+            #
+            # For height, this only needs to be done if the
+            # subplots share a column.  For width if they
+            # share a row.
+
+            drowsC = (rownumCmax - rownumCmin + 1)
+            drows0 = (rownum0max - rownum0min + 1)
+            dcolsC = (colnumCmax - colnumCmin + 1)
+            dcols0 = (colnum0max - colnum0min + 1)
+
+            if not alignheight and drows0 == drowsC:
+                ax._poslayoutbox.constrain_height(
+                        axc._poslayoutbox.height * height0 / heightC)
+                alignheight = True
+            elif _in_same_column(colnum0min, colnum0max,
+                    colnumCmin, colnumCmax):
+                if height0 > heightC:
+                    ax._poslayoutbox.constrain_height_min(
+                        axc._poslayoutbox.height * height0 / heightC)
+                    # these constraints stop the smaller axes from
+                    # being allowed to go to zero height...
+                    axc._poslayoutbox.constrain_height_min(
+                        ax._poslayoutbox.height * heightC /
+                        (height0*1.8))
+                elif height0 < heightC:
+                    axc._poslayoutbox.constrain_height_min(
+                        ax._poslayoutbox.height * heightC / height0)
+                    ax._poslayoutbox.constrain_height_min(
+                        ax._poslayoutbox.height * height0 /
+                        (heightC*1.8))
+            # widths...
+            if not alignwidth and dcols0 == dcolsC:
+                ax._poslayoutbox.constrain_width(
+                        axc._poslayoutbox.width * width0 / widthC)
+                alignwidth = True
+            elif _in_same_row(rownum0min, rownum0max,
+                    rownumCmin, rownumCmax):
+                if width0 > widthC:
+                    ax._poslayoutbox.constrain_width_min(
+                            axc._poslayoutbox.width * width0 / widthC)
+                    axc._poslayoutbox.constrain_width_min(
+                            ax._poslayoutbox.width * widthC /
+                            (width0*1.8))
+                elif width0 < widthC:
+                    axc._poslayoutbox.constrain_width_min(
+                            ax._poslayoutbox.width * widthC / width0)
+                    ax._poslayoutbox.constrain_width_min(
+                            axc._poslayoutbox.width * width0 /
+                            (widthC*1.8))
+
+
+def _arrange_subplotspecs(gs, hspace=0, wspace=0):
+    """Recursively arrange the subplotspec children of the given gridspec."""
+    sschildren = []
+    for child in gs.children:
+        if child._is_subplotspec_layoutbox():
+            for child2 in child.children:
+                # check for gridspec children...
+                if child2._is_gridspec_layoutbox():
+                    _arrange_subplotspecs(child2, hspace=hspace, wspace=wspace)
+            sschildren += [child]
+    # now arrange the subplots...
+    for child0 in sschildren:
+        ss0 = child0.artist
+        nrows, ncols = ss0.get_gridspec().get_geometry()
+        rowNum0min, colNum0min = divmod(ss0.num1, ncols)
+        rowNum0max, colNum0max = divmod(ss0.num2, ncols)
+        sschildren = sschildren[1:]
+        for childc in sschildren:
+            ssc = childc.artist
+            rowNumCmin, colNumCmin = divmod(ssc.num1, ncols)
+            rowNumCmax, colNumCmax = divmod(ssc.num2, ncols)
+            # OK, this tells us the relative layout of ax
+            # with axc
+            thepad = wspace / ncols
+            if colNum0max < colNumCmin:
+                layoutbox.hstack([ss0._layoutbox, ssc._layoutbox],
+                        padding=thepad)
+            if colNumCmax < colNum0min:
+                layoutbox.hstack([ssc._layoutbox, ss0._layoutbox],
+                        padding=thepad)
+
+            ####
+            # vertical alignment
+            thepad = hspace / nrows
+            if rowNum0max < rowNumCmin:
+                layoutbox.vstack([ss0._layoutbox,
+                                 ssc._layoutbox],
+                                 padding=thepad)
+            if rowNumCmax < rowNum0min:
+                layoutbox.vstack([ssc._layoutbox,
+                                  ss0._layoutbox],
+                                  padding=thepad)
+
+
+def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
+    """
+    Do the layout for a colorbar, to not overly pollute colorbar.py
+
+    *pad* is in fraction of the original axis size.
+    """
+    axlb = ax._layoutbox
+    axpos = ax._poslayoutbox
+    axsslb = ax.get_subplotspec()._layoutbox
+    lb = layoutbox.LayoutBox(
+            parent=axsslb,
+            name=axsslb.name + '.cbar',
+            artist=cax)
+
+    if location in ('left', 'right'):
+        lbpos = layoutbox.LayoutBox(
+                parent=lb,
+                name=lb.name + '.pos',
+                tightwidth=False,
+                pos=True,
+                subplot=False,
+                artist=cax)
+
+        if location == 'right':
+            # arrange to right of parent axis
+            layoutbox.hstack([axlb, lb], padding=pad * axlb.width,
+                             strength='strong')
+        else:
+            layoutbox.hstack([lb, axlb], padding=pad * axlb.width)
+        # constrain the height and center...
+        layoutbox.match_heights([axpos, lbpos], [1, shrink])
+        layoutbox.align([axpos, lbpos], 'v_center')
+        # set the width of the pos box
+        lbpos.constrain_width(shrink * axpos.height * (1/aspect),
+                              strength='strong')
+    elif location in ('bottom', 'top'):
+        lbpos = layoutbox.LayoutBox(
+                parent=lb,
+                name=lb.name + '.pos',
+                tightheight=True,
+                pos=True,
+                subplot=False,
+                artist=cax)
+
+        if location == 'bottom':
+            layoutbox.vstack([axlb, lb], padding=pad * axlb.height)
+        else:
+            layoutbox.vstack([lb, axlb], padding=pad * axlb.height)
+        # constrain the height and center...
+        layoutbox.match_widths([axpos, lbpos],
+                               [1, shrink], strength='strong')
+        layoutbox.align([axpos, lbpos], 'h_center')
+        # set the height of the pos box
+        lbpos.constrain_height(axpos.width * aspect * shrink,
+                                strength='medium')
+
+    return lb, lbpos
+
+
+def _getmaxminrowcolumn(axs):
+    # helper to get the min/max rows and columns of a list of axes.
+    maxrow = -100000
+    minrow = 1000000
+    maxax = None
+    minax = None
+    maxcol = -100000
+    mincol = 1000000
+    maxax_col = None
+    minax_col = None
+
+    for ax in axs:
+        subspec = ax.get_subplotspec()
+        nrows, ncols, row_start, row_stop, col_start, col_stop = \
+            subspec.get_rows_columns()
+        if row_stop > maxrow:
+            maxrow = row_stop
+            maxax = ax
+        if row_start < minrow:
+            minrow = row_start
+            minax = ax
+        if col_stop > maxcol:
+            maxcol = col_stop
+            maxax_col = ax
+        if col_start < mincol:
+            mincol = col_start
+            minax_col = ax
+    return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col)
+
+
+def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
+    """
+    Do the layout for a colorbar, to not overly pollute colorbar.py
+
+    *pad* is in fraction of the original axis size.
+    """
+
+    gs = parents[0].get_subplotspec().get_gridspec()
+    # parent layout box....
+    gslb = gs._layoutbox
+
+    lb = layoutbox.LayoutBox(parent=gslb.parent,
+                             name=gslb.parent.name + '.cbar',
+                             artist=cax)
+    # figure out the row and column extent of the parents.
+    (minrow, maxrow, minax_row, maxax_row,
+     mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
+
+    if location in ('left', 'right'):
+        lbpos = layoutbox.LayoutBox(
+                parent=lb,
+                name=lb.name + '.pos',
+                tightwidth=False,
+                pos=True,
+                subplot=False,
+                artist=cax)
+        for ax in parents:
+            if location == 'right':
+                order = [ax._layoutbox, lb]
+            else:
+                order = [lb, ax._layoutbox]
+            layoutbox.hstack(order, padding=pad * gslb.width,
+                         strength='strong')
+        # constrain the height and center...
+        # This isn't quite right.  We'd like the colorbar
+        # pos to line up w/ the axes poss, not the size of the
+        # gs.
+
+        # Horizontal Layout: need to check all the axes in this gridspec
+        for ch in gslb.children:
+            subspec = ch.artist
+            nrows, ncols, row_start, row_stop, col_start, col_stop = \
+                subspec.get_rows_columns()
+            if location == 'right':
+                if col_stop <= maxcol:
+                    order = [subspec._layoutbox, lb]
+                    # arrange to right of the parents
+                if col_start > maxcol:
+                    order = [lb, subspec._layoutbox]
+            elif location == 'left':
+                if col_start >= mincol:
+                    order = [lb, subspec._layoutbox]
+                if col_stop < mincol:
+                    order = [subspec._layoutbox, lb]
+            layoutbox.hstack(order, padding=pad * gslb.width,
+                             strength='strong')
+
+        # Vertical layout:
+        maxposlb = minax_row._poslayoutbox
+        minposlb = maxax_row._poslayoutbox
+        # now we want the height of the colorbar pos to be
+        # set by the top and bottom of the min/max axes...
+        # bottom            top
+        #     b             t
+        # h = (top-bottom)*shrink
+        # b = bottom + (top-bottom - h) / 2.
+        lbpos.constrain_height(
+                (maxposlb.top - minposlb.bottom) *
+                shrink, strength='strong')
+        lbpos.constrain_bottom(
+                (maxposlb.top - minposlb.bottom) *
+                (1 - shrink)/2 + minposlb.bottom,
+                strength='strong')
+
+        # set the width of the pos box
+        lbpos.constrain_width(lbpos.height * (shrink / aspect),
+                              strength='strong')
+    elif location in ('bottom', 'top'):
+        lbpos = layoutbox.LayoutBox(
+                parent=lb,
+                name=lb.name + '.pos',
+                tightheight=True,
+                pos=True,
+                subplot=False,
+                artist=cax)
+
+        for ax in parents:
+            if location == 'bottom':
+                order = [ax._layoutbox, lb]
+            else:
+                order = [lb, ax._layoutbox]
+            layoutbox.vstack(order, padding=pad * gslb.width,
+                         strength='strong')
+
+        # Vertical Layout: need to check all the axes in this gridspec
+        for ch in gslb.children:
+            subspec = ch.artist
+            nrows, ncols, row_start, row_stop, col_start, col_stop = \
+                subspec.get_rows_columns()
+            if location == 'bottom':
+                if row_stop <= minrow:
+                    order = [subspec._layoutbox, lb]
+                if row_start > maxrow:
+                    order = [lb, subspec._layoutbox]
+            elif location == 'top':
+                if row_stop < minrow:
+                    order = [subspec._layoutbox, lb]
+                if row_start >= maxrow:
+                    order = [lb, subspec._layoutbox]
+            layoutbox.vstack(order, padding=pad * gslb.width,
+                             strength='strong')
+
+        # Do horizontal layout...
+        maxposlb = maxax_col._poslayoutbox
+        minposlb = minax_col._poslayoutbox
+        lbpos.constrain_width((maxposlb.right - minposlb.left) *
+                              shrink)
+        lbpos.constrain_left(
+                (maxposlb.right - minposlb.left) *
+                (1-shrink)/2 + minposlb.left)
+        # set the height of the pos box
+        lbpos.constrain_height(lbpos.width * shrink * aspect,
+                               strength='medium')
+
+    return lb, lbpos

BIN
venv/lib/python3.8/site-packages/matplotlib/_contour.cpython-38-x86_64-linux-gnu.so


BIN
venv/lib/python3.8/site-packages/matplotlib/_image.cpython-38-x86_64-linux-gnu.so


+ 711 - 0
venv/lib/python3.8/site-packages/matplotlib/_layoutbox.py

@@ -0,0 +1,711 @@
+"""
+
+Conventions:
+
+"constrain_x" means to constrain the variable with either
+another kiwisolver variable, or a float.  i.e. `constrain_width(0.2)`
+will set a constraint that the width has to be 0.2 and this constraint is
+permanent - i.e. it will not be removed if it becomes obsolete.
+
+"edit_x" means to set x to a value (just a float), and that this value can
+change.  So `edit_width(0.2)` will set width to be 0.2, but `edit_width(0.3)`
+will allow it to change to 0.3 later.  Note that these values are still just
+"suggestions" in `kiwisolver` parlance, and could be over-ridden by
+other constrains.
+
+"""
+
+import itertools
+import kiwisolver as kiwi
+import logging
+import numpy as np
+
+
+_log = logging.getLogger(__name__)
+
+
+# renderers can be complicated
+def get_renderer(fig):
+    if fig._cachedRenderer:
+        renderer = fig._cachedRenderer
+    else:
+        canvas = fig.canvas
+        if canvas and hasattr(canvas, "get_renderer"):
+            renderer = canvas.get_renderer()
+        else:
+            # not sure if this can happen
+            # seems to with PDF...
+            _log.info("constrained_layout : falling back to Agg renderer")
+            from matplotlib.backends.backend_agg import FigureCanvasAgg
+            canvas = FigureCanvasAgg(fig)
+            renderer = canvas.get_renderer()
+
+    return renderer
+
+
+class LayoutBox:
+    """
+    Basic rectangle representation using kiwi solver variables
+    """
+
+    def __init__(self, parent=None, name='', tightwidth=False,
+                 tightheight=False, artist=None,
+                 lower_left=(0, 0), upper_right=(1, 1), pos=False,
+                 subplot=False, h_pad=None, w_pad=None):
+        Variable = kiwi.Variable
+        self.parent = parent
+        self.name = name
+        sn = self.name + '_'
+        if parent is None:
+            self.solver = kiwi.Solver()
+            self.constrained_layout_called = 0
+        else:
+            self.solver = parent.solver
+            self.constrained_layout_called = None
+            # parent wants to know about this child!
+            parent.add_child(self)
+        # keep track of artist associated w/ this layout.  Can be none
+        self.artist = artist
+        # keep track if this box is supposed to be a pos that is constrained
+        # by the parent.
+        self.pos = pos
+        # keep track of whether we need to match this subplot up with others.
+        self.subplot = subplot
+
+        # we need the str below for Py 2 which complains the string is unicode
+        self.top = Variable(str(sn + 'top'))
+        self.bottom = Variable(str(sn + 'bottom'))
+        self.left = Variable(str(sn + 'left'))
+        self.right = Variable(str(sn + 'right'))
+
+        self.width = Variable(str(sn + 'width'))
+        self.height = Variable(str(sn + 'height'))
+        self.h_center = Variable(str(sn + 'h_center'))
+        self.v_center = Variable(str(sn + 'v_center'))
+
+        self.min_width = Variable(str(sn + 'min_width'))
+        self.min_height = Variable(str(sn + 'min_height'))
+        self.pref_width = Variable(str(sn + 'pref_width'))
+        self.pref_height = Variable(str(sn + 'pref_height'))
+        # margins are only used for axes-position layout boxes.  maybe should
+        # be a separate subclass:
+        self.left_margin = Variable(str(sn + 'left_margin'))
+        self.right_margin = Variable(str(sn + 'right_margin'))
+        self.bottom_margin = Variable(str(sn + 'bottom_margin'))
+        self.top_margin = Variable(str(sn + 'top_margin'))
+        # mins
+        self.left_margin_min = Variable(str(sn + 'left_margin_min'))
+        self.right_margin_min = Variable(str(sn + 'right_margin_min'))
+        self.bottom_margin_min = Variable(str(sn + 'bottom_margin_min'))
+        self.top_margin_min = Variable(str(sn + 'top_margin_min'))
+
+        right, top = upper_right
+        left, bottom = lower_left
+        self.tightheight = tightheight
+        self.tightwidth = tightwidth
+        self.add_constraints()
+        self.children = []
+        self.subplotspec = None
+        if self.pos:
+            self.constrain_margins()
+        self.h_pad = h_pad
+        self.w_pad = w_pad
+
+    def constrain_margins(self):
+        """
+        Only do this for pos.  This sets a variable distance
+        margin between the position of the axes and the outer edge of
+        the axes.
+
+        Margins are variable because they change with the figure size.
+
+        Margin minimums are set to make room for axes decorations.  However,
+        the margins can be larger if we are mathicng the position size to
+        other axes.
+        """
+        sol = self.solver
+
+        # left
+        if not sol.hasEditVariable(self.left_margin_min):
+            sol.addEditVariable(self.left_margin_min, 'strong')
+            sol.suggestValue(self.left_margin_min, 0.0001)
+        c = (self.left_margin == self.left - self.parent.left)
+        self.solver.addConstraint(c | 'required')
+        c = (self.left_margin >= self.left_margin_min)
+        self.solver.addConstraint(c | 'strong')
+
+        # right
+        if not sol.hasEditVariable(self.right_margin_min):
+            sol.addEditVariable(self.right_margin_min, 'strong')
+            sol.suggestValue(self.right_margin_min, 0.0001)
+        c = (self.right_margin == self.parent.right - self.right)
+        self.solver.addConstraint(c | 'required')
+        c = (self.right_margin >= self.right_margin_min)
+        self.solver.addConstraint(c | 'required')
+        # bottom
+        if not sol.hasEditVariable(self.bottom_margin_min):
+            sol.addEditVariable(self.bottom_margin_min, 'strong')
+            sol.suggestValue(self.bottom_margin_min, 0.0001)
+        c = (self.bottom_margin == self.bottom - self.parent.bottom)
+        self.solver.addConstraint(c | 'required')
+        c = (self.bottom_margin >= self.bottom_margin_min)
+        self.solver.addConstraint(c | 'required')
+        # top
+        if not sol.hasEditVariable(self.top_margin_min):
+            sol.addEditVariable(self.top_margin_min, 'strong')
+            sol.suggestValue(self.top_margin_min, 0.0001)
+        c = (self.top_margin == self.parent.top - self.top)
+        self.solver.addConstraint(c | 'required')
+        c = (self.top_margin >= self.top_margin_min)
+        self.solver.addConstraint(c | 'required')
+
+    def add_child(self, child):
+        self.children += [child]
+
+    def remove_child(self, child):
+        try:
+            self.children.remove(child)
+        except ValueError:
+            _log.info("Tried to remove child that doesn't belong to parent")
+
+    def add_constraints(self):
+        sol = self.solver
+        # never let width and height go negative.
+        for i in [self.min_width, self.min_height]:
+            sol.addEditVariable(i, 1e9)
+            sol.suggestValue(i, 0.0)
+        # define relation ships between things thing width and right and left
+        self.hard_constraints()
+        # self.soft_constraints()
+        if self.parent:
+            self.parent_constrain()
+        # sol.updateVariables()
+
+    def parent_constrain(self):
+        parent = self.parent
+        hc = [self.left >= parent.left,
+              self.bottom >= parent.bottom,
+              self.top <= parent.top,
+              self.right <= parent.right]
+        for c in hc:
+            self.solver.addConstraint(c | 'required')
+
+    def hard_constraints(self):
+        hc = [self.width == self.right - self.left,
+              self.height == self.top - self.bottom,
+              self.h_center == (self.left + self.right) * 0.5,
+              self.v_center == (self.top + self.bottom) * 0.5,
+              self.width >= self.min_width,
+              self.height >= self.min_height]
+        for c in hc:
+            self.solver.addConstraint(c | 'required')
+
+    def soft_constraints(self):
+        sol = self.solver
+        if self.tightwidth:
+            suggest = 0.
+        else:
+            suggest = 20.
+        c = (self.pref_width == suggest)
+        for i in c:
+            sol.addConstraint(i | 'required')
+        if self.tightheight:
+            suggest = 0.
+        else:
+            suggest = 20.
+        c = (self.pref_height == suggest)
+        for i in c:
+            sol.addConstraint(i | 'required')
+
+        c = [(self.width >= suggest),
+             (self.height >= suggest)]
+        for i in c:
+            sol.addConstraint(i | 150000)
+
+    def set_parent(self, parent):
+        """Replace the parent of this with the new parent."""
+        self.parent = parent
+        self.parent_constrain()
+
+    def constrain_geometry(self, left, bottom, right, top, strength='strong'):
+        hc = [self.left == left,
+              self.right == right,
+              self.bottom == bottom,
+              self.top == top]
+        for c in hc:
+            self.solver.addConstraint(c | strength)
+        # self.solver.updateVariables()
+
+    def constrain_same(self, other, strength='strong'):
+        """
+        Make the layoutbox have same position as other layoutbox
+        """
+        hc = [self.left == other.left,
+              self.right == other.right,
+              self.bottom == other.bottom,
+              self.top == other.top]
+        for c in hc:
+            self.solver.addConstraint(c | strength)
+
+    def constrain_left_margin(self, margin, strength='strong'):
+        c = (self.left == self.parent.left + margin)
+        self.solver.addConstraint(c | strength)
+
+    def edit_left_margin_min(self, margin):
+        self.solver.suggestValue(self.left_margin_min, margin)
+
+    def constrain_right_margin(self, margin, strength='strong'):
+        c = (self.right == self.parent.right - margin)
+        self.solver.addConstraint(c | strength)
+
+    def edit_right_margin_min(self, margin):
+        self.solver.suggestValue(self.right_margin_min, margin)
+
+    def constrain_bottom_margin(self, margin, strength='strong'):
+        c = (self.bottom == self.parent.bottom + margin)
+        self.solver.addConstraint(c | strength)
+
+    def edit_bottom_margin_min(self, margin):
+        self.solver.suggestValue(self.bottom_margin_min, margin)
+
+    def constrain_top_margin(self, margin, strength='strong'):
+        c = (self.top == self.parent.top - margin)
+        self.solver.addConstraint(c | strength)
+
+    def edit_top_margin_min(self, margin):
+        self.solver.suggestValue(self.top_margin_min, margin)
+
+    def get_rect(self):
+        return (self.left.value(), self.bottom.value(),
+                self.width.value(), self.height.value())
+
+    def update_variables(self):
+        '''
+        Update *all* the variables that are part of the solver this LayoutBox
+        is created with
+        '''
+        self.solver.updateVariables()
+
+    def edit_height(self, height, strength='strong'):
+        '''
+        Set the height of the layout box.
+
+        This is done as an editable variable so that the value can change
+        due to resizing.
+        '''
+        sol = self.solver
+        for i in [self.height]:
+            if not sol.hasEditVariable(i):
+                sol.addEditVariable(i, strength)
+        sol.suggestValue(self.height, height)
+
+    def constrain_height(self, height, strength='strong'):
+        '''
+        Constrain the height of the layout box.  height is
+        either a float or a layoutbox.height.
+        '''
+        c = (self.height == height)
+        self.solver.addConstraint(c | strength)
+
+    def constrain_height_min(self, height, strength='strong'):
+        c = (self.height >= height)
+        self.solver.addConstraint(c | strength)
+
+    def edit_width(self, width, strength='strong'):
+        sol = self.solver
+        for i in [self.width]:
+            if not sol.hasEditVariable(i):
+                sol.addEditVariable(i, strength)
+        sol.suggestValue(self.width, width)
+
+    def constrain_width(self, width, strength='strong'):
+        """
+        Constrain the width of the layout box.  *width* is
+        either a float or a layoutbox.width.
+        """
+        c = (self.width == width)
+        self.solver.addConstraint(c | strength)
+
+    def constrain_width_min(self, width, strength='strong'):
+        c = (self.width >= width)
+        self.solver.addConstraint(c | strength)
+
+    def constrain_left(self, left,  strength='strong'):
+        c = (self.left == left)
+        self.solver.addConstraint(c | strength)
+
+    def constrain_bottom(self, bottom, strength='strong'):
+        c = (self.bottom == bottom)
+        self.solver.addConstraint(c | strength)
+
+    def constrain_right(self, right, strength='strong'):
+        c = (self.right == right)
+        self.solver.addConstraint(c | strength)
+
+    def constrain_top(self, top, strength='strong'):
+        c = (self.top == top)
+        self.solver.addConstraint(c | strength)
+
+    def _is_subplotspec_layoutbox(self):
+        '''
+        Helper to check if this layoutbox is the layoutbox of a
+        subplotspec
+        '''
+        name = (self.name).split('.')[-1]
+        return name[:2] == 'ss'
+
+    def _is_gridspec_layoutbox(self):
+        '''
+        Helper to check if this layoutbox is the layoutbox of a
+        gridspec
+        '''
+        name = (self.name).split('.')[-1]
+        return name[:8] == 'gridspec'
+
+    def find_child_subplots(self):
+        '''
+        Find children of this layout box that are subplots.  We want to line
+        poss up, and this is an easy way to find them all.
+        '''
+        if self.subplot:
+            subplots = [self]
+        else:
+            subplots = []
+        for child in self.children:
+            subplots += child.find_child_subplots()
+        return subplots
+
+    def layout_from_subplotspec(self, subspec,
+                                name='', artist=None, pos=False):
+        """
+        Make a layout box from a subplotspec. The layout box is
+        constrained to be a fraction of the width/height of the parent,
+        and be a fraction of the parent width/height from the left/bottom
+        of the parent.  Therefore the parent can move around and the
+        layout for the subplot spec should move with it.
+
+        The parent is *usually* the gridspec that made the subplotspec.??
+        """
+        lb = LayoutBox(parent=self, name=name, artist=artist, pos=pos)
+        gs = subspec.get_gridspec()
+        nrows, ncols = gs.get_geometry()
+        parent = self.parent
+
+        # OK, now, we want to set the position of this subplotspec
+        # based on its subplotspec parameters.  The new gridspec will inherit
+        # from gridspec.  prob should be new method in gridspec
+        left = 0.0
+        right = 1.0
+        bottom = 0.0
+        top = 1.0
+        totWidth = right-left
+        totHeight = top-bottom
+        hspace = 0.
+        wspace = 0.
+
+        # calculate accumulated heights of columns
+        cellH = totHeight / (nrows + hspace * (nrows - 1))
+        sepH = hspace * cellH
+
+        if gs._row_height_ratios is not None:
+            netHeight = cellH * nrows
+            tr = sum(gs._row_height_ratios)
+            cellHeights = [netHeight * r / tr for r in gs._row_height_ratios]
+        else:
+            cellHeights = [cellH] * nrows
+
+        sepHeights = [0] + ([sepH] * (nrows - 1))
+        cellHs = np.cumsum(np.column_stack([sepHeights, cellHeights]).flat)
+
+        # calculate accumulated widths of rows
+        cellW = totWidth / (ncols + wspace * (ncols - 1))
+        sepW = wspace * cellW
+
+        if gs._col_width_ratios is not None:
+            netWidth = cellW * ncols
+            tr = sum(gs._col_width_ratios)
+            cellWidths = [netWidth * r / tr for r in gs._col_width_ratios]
+        else:
+            cellWidths = [cellW] * ncols
+
+        sepWidths = [0] + ([sepW] * (ncols - 1))
+        cellWs = np.cumsum(np.column_stack([sepWidths, cellWidths]).flat)
+
+        figTops = [top - cellHs[2 * rowNum] for rowNum in range(nrows)]
+        figBottoms = [top - cellHs[2 * rowNum + 1] for rowNum in range(nrows)]
+        figLefts = [left + cellWs[2 * colNum] for colNum in range(ncols)]
+        figRights = [left + cellWs[2 * colNum + 1] for colNum in range(ncols)]
+
+        rowNum1, colNum1 = divmod(subspec.num1, ncols)
+        rowNum2, colNum2 = divmod(subspec.num2, ncols)
+        figBottom = min(figBottoms[rowNum1], figBottoms[rowNum2])
+        figTop = max(figTops[rowNum1], figTops[rowNum2])
+        figLeft = min(figLefts[colNum1], figLefts[colNum2])
+        figRight = max(figRights[colNum1], figRights[colNum2])
+
+        # These are numbers relative to (0, 0, 1, 1).  Need to constrain
+        # relative to parent.
+
+        width = figRight - figLeft
+        height = figTop - figBottom
+        parent = self.parent
+        cs = [self.left == parent.left + parent.width * figLeft,
+              self.bottom == parent.bottom + parent.height * figBottom,
+              self.width == parent.width * width,
+              self.height == parent.height * height]
+        for c in cs:
+            self.solver.addConstraint(c | 'required')
+
+        return lb
+
+    def __repr__(self):
+        args = (self.name, self.left.value(), self.bottom.value(),
+                self.right.value(), self.top.value())
+        return ('LayoutBox: %25s, (left: %1.3f) (bot: %1.3f) '
+               '(right: %1.3f)  (top: %1.3f) ') % args
+
+
+# Utility functions that act on layoutboxes...
+def hstack(boxes, padding=0, strength='strong'):
+    '''
+    Stack LayoutBox instances from left to right.
+    *padding* is in figure-relative units.
+    '''
+
+    for i in range(1, len(boxes)):
+        c = (boxes[i-1].right + padding <= boxes[i].left)
+        boxes[i].solver.addConstraint(c | strength)
+
+
+def hpack(boxes, padding=0, strength='strong'):
+    '''
+    Stack LayoutBox instances from left to right.
+    '''
+
+    for i in range(1, len(boxes)):
+        c = (boxes[i-1].right + padding == boxes[i].left)
+        boxes[i].solver.addConstraint(c | strength)
+
+
+def vstack(boxes, padding=0, strength='strong'):
+    '''
+    Stack LayoutBox instances from top to bottom
+    '''
+
+    for i in range(1, len(boxes)):
+        c = (boxes[i-1].bottom - padding >= boxes[i].top)
+        boxes[i].solver.addConstraint(c | strength)
+
+
+def vpack(boxes, padding=0, strength='strong'):
+    '''
+    Stack LayoutBox instances from top to bottom
+    '''
+
+    for i in range(1, len(boxes)):
+        c = (boxes[i-1].bottom - padding >= boxes[i].top)
+        boxes[i].solver.addConstraint(c | strength)
+
+
+def match_heights(boxes, height_ratios=None, strength='medium'):
+    '''
+    Stack LayoutBox instances from top to bottom
+    '''
+
+    if height_ratios is None:
+        height_ratios = np.ones(len(boxes))
+    for i in range(1, len(boxes)):
+        c = (boxes[i-1].height ==
+             boxes[i].height*height_ratios[i-1]/height_ratios[i])
+        boxes[i].solver.addConstraint(c | strength)
+
+
+def match_widths(boxes, width_ratios=None, strength='medium'):
+    '''
+    Stack LayoutBox instances from top to bottom
+    '''
+
+    if width_ratios is None:
+        width_ratios = np.ones(len(boxes))
+    for i in range(1, len(boxes)):
+        c = (boxes[i-1].width ==
+             boxes[i].width*width_ratios[i-1]/width_ratios[i])
+        boxes[i].solver.addConstraint(c | strength)
+
+
+def vstackeq(boxes, padding=0, height_ratios=None):
+    vstack(boxes, padding=padding)
+    match_heights(boxes, height_ratios=height_ratios)
+
+
+def hstackeq(boxes, padding=0, width_ratios=None):
+    hstack(boxes, padding=padding)
+    match_widths(boxes, width_ratios=width_ratios)
+
+
+def align(boxes, attr, strength='strong'):
+    cons = []
+    for box in boxes[1:]:
+        cons = (getattr(boxes[0], attr) == getattr(box, attr))
+        boxes[0].solver.addConstraint(cons | strength)
+
+
+def match_top_margins(boxes, levels=1):
+    box0 = boxes[0]
+    top0 = box0
+    for n in range(levels):
+        top0 = top0.parent
+    for box in boxes[1:]:
+        topb = box
+        for n in range(levels):
+            topb = topb.parent
+        c = (box0.top-top0.top == box.top-topb.top)
+        box0.solver.addConstraint(c | 'strong')
+
+
+def match_bottom_margins(boxes, levels=1):
+    box0 = boxes[0]
+    top0 = box0
+    for n in range(levels):
+        top0 = top0.parent
+    for box in boxes[1:]:
+        topb = box
+        for n in range(levels):
+            topb = topb.parent
+        c = (box0.bottom-top0.bottom == box.bottom-topb.bottom)
+        box0.solver.addConstraint(c | 'strong')
+
+
+def match_left_margins(boxes, levels=1):
+    box0 = boxes[0]
+    top0 = box0
+    for n in range(levels):
+        top0 = top0.parent
+    for box in boxes[1:]:
+        topb = box
+        for n in range(levels):
+            topb = topb.parent
+        c = (box0.left-top0.left == box.left-topb.left)
+        box0.solver.addConstraint(c | 'strong')
+
+
+def match_right_margins(boxes, levels=1):
+    box0 = boxes[0]
+    top0 = box0
+    for n in range(levels):
+        top0 = top0.parent
+    for box in boxes[1:]:
+        topb = box
+        for n in range(levels):
+            topb = topb.parent
+        c = (box0.right-top0.right == box.right-topb.right)
+        box0.solver.addConstraint(c | 'strong')
+
+
+def match_width_margins(boxes, levels=1):
+    match_left_margins(boxes, levels=levels)
+    match_right_margins(boxes, levels=levels)
+
+
+def match_height_margins(boxes, levels=1):
+    match_top_margins(boxes, levels=levels)
+    match_bottom_margins(boxes, levels=levels)
+
+
+def match_margins(boxes, levels=1):
+    match_width_margins(boxes, levels=levels)
+    match_height_margins(boxes, levels=levels)
+
+
+_layoutboxobjnum = itertools.count()
+
+
+def seq_id():
+    """Generate a short sequential id for layoutbox objects."""
+    return '%06d' % next(_layoutboxobjnum)
+
+
+def print_children(lb):
+    """Print the children of the layoutbox."""
+    print(lb)
+    for child in lb.children:
+        print_children(child)
+
+
+def nonetree(lb):
+    """
+    Make all elements in this tree None, signalling not to do any more layout.
+    """
+    if lb is not None:
+        if lb.parent is None:
+            # Clear the solver.  Hopefully this garbage collects.
+            lb.solver.reset()
+            nonechildren(lb)
+        else:
+            nonetree(lb.parent)
+
+
+def nonechildren(lb):
+    for child in lb.children:
+        nonechildren(child)
+    lb.artist._layoutbox = None
+    lb = None
+
+
+def print_tree(lb):
+    '''
+    Print the tree of layoutboxes
+    '''
+
+    if lb.parent is None:
+        print('LayoutBox Tree\n')
+        print('==============\n')
+        print_children(lb)
+        print('\n')
+    else:
+        print_tree(lb.parent)
+
+
+def plot_children(fig, box, level=0, printit=True):
+    '''
+    Simple plotting to show where boxes are
+    '''
+    import matplotlib
+    import matplotlib.pyplot as plt
+
+    if isinstance(fig, matplotlib.figure.Figure):
+        ax = fig.add_axes([0., 0., 1., 1.])
+        ax.set_facecolor([1., 1., 1., 0.7])
+        ax.set_alpha(0.3)
+        fig.draw(fig.canvas.get_renderer())
+    else:
+        ax = fig
+
+    import matplotlib.patches as patches
+    colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
+    if printit:
+        print("Level:", level)
+    for child in box.children:
+        if printit:
+            print(child)
+        ax.add_patch(
+            patches.Rectangle(
+                (child.left.value(), child.bottom.value()),  # (x, y)
+                child.width.value(),  # width
+                child.height.value(),  # height
+                fc='none',
+                alpha=0.8,
+                ec=colors[level]
+            )
+        )
+        if level > 0:
+            name = child.name.split('.')[-1]
+            if level % 2 == 0:
+                ax.text(child.left.value(), child.bottom.value(), name,
+                        size=12-level, color=colors[level])
+            else:
+                ax.text(child.right.value(), child.top.value(), name,
+                        ha='right', va='top', size=12-level,
+                        color=colors[level])
+
+        plot_children(ax, child, level=level+1, printit=printit)

+ 2544 - 0
venv/lib/python3.8/site-packages/matplotlib/_mathtext_data.py

@@ -0,0 +1,2544 @@
+"""
+font data tables for truetype and afm computer modern fonts
+"""
+
+latex_to_bakoma = {
+    '\\__sqrt__'                 : ('cmex10', 0x70),
+    '\\bigcap'                   : ('cmex10', 0x5c),
+    '\\bigcup'                   : ('cmex10', 0x5b),
+    '\\bigodot'                  : ('cmex10', 0x4b),
+    '\\bigoplus'                 : ('cmex10', 0x4d),
+    '\\bigotimes'                : ('cmex10', 0x4f),
+    '\\biguplus'                 : ('cmex10', 0x5d),
+    '\\bigvee'                   : ('cmex10', 0x5f),
+    '\\bigwedge'                 : ('cmex10', 0x5e),
+    '\\coprod'                   : ('cmex10', 0x61),
+    '\\int'                      : ('cmex10', 0x5a),
+    '\\langle'                   : ('cmex10', 0xad),
+    '\\leftangle'                : ('cmex10', 0xad),
+    '\\leftbrace'                : ('cmex10', 0xa9),
+    '\\oint'                     : ('cmex10', 0x49),
+    '\\prod'                     : ('cmex10', 0x59),
+    '\\rangle'                   : ('cmex10', 0xae),
+    '\\rightangle'               : ('cmex10', 0xae),
+    '\\rightbrace'               : ('cmex10', 0xaa),
+    '\\sum'                      : ('cmex10', 0x58),
+    '\\widehat'                  : ('cmex10', 0x62),
+    '\\widetilde'                : ('cmex10', 0x65),
+    '\\{'                        : ('cmex10', 0xa9),
+    '\\}'                        : ('cmex10', 0xaa),
+    '{'                          : ('cmex10', 0xa9),
+    '}'                          : ('cmex10', 0xaa),
+
+    ','                          : ('cmmi10', 0x3b),
+    '.'                          : ('cmmi10', 0x3a),
+    '/'                          : ('cmmi10', 0x3d),
+    '<'                          : ('cmmi10', 0x3c),
+    '>'                          : ('cmmi10', 0x3e),
+    '\\alpha'                    : ('cmmi10', 0xae),
+    '\\beta'                     : ('cmmi10', 0xaf),
+    '\\chi'                      : ('cmmi10', 0xc2),
+    '\\combiningrightarrowabove' : ('cmmi10', 0x7e),
+    '\\delta'                    : ('cmmi10', 0xb1),
+    '\\ell'                      : ('cmmi10', 0x60),
+    '\\epsilon'                  : ('cmmi10', 0xb2),
+    '\\eta'                      : ('cmmi10', 0xb4),
+    '\\flat'                     : ('cmmi10', 0x5b),
+    '\\frown'                    : ('cmmi10', 0x5f),
+    '\\gamma'                    : ('cmmi10', 0xb0),
+    '\\imath'                    : ('cmmi10', 0x7b),
+    '\\iota'                     : ('cmmi10', 0xb6),
+    '\\jmath'                    : ('cmmi10', 0x7c),
+    '\\kappa'                    : ('cmmi10', 0x2219),
+    '\\lambda'                   : ('cmmi10', 0xb8),
+    '\\leftharpoondown'          : ('cmmi10', 0x29),
+    '\\leftharpoonup'            : ('cmmi10', 0x28),
+    '\\mu'                       : ('cmmi10', 0xb9),
+    '\\natural'                  : ('cmmi10', 0x5c),
+    '\\nu'                       : ('cmmi10', 0xba),
+    '\\omega'                    : ('cmmi10', 0x21),
+    '\\phi'                      : ('cmmi10', 0xc1),
+    '\\pi'                       : ('cmmi10', 0xbc),
+    '\\psi'                      : ('cmmi10', 0xc3),
+    '\\rho'                      : ('cmmi10', 0xbd),
+    '\\rightharpoondown'         : ('cmmi10', 0x2b),
+    '\\rightharpoonup'           : ('cmmi10', 0x2a),
+    '\\sharp'                    : ('cmmi10', 0x5d),
+    '\\sigma'                    : ('cmmi10', 0xbe),
+    '\\smile'                    : ('cmmi10', 0x5e),
+    '\\tau'                      : ('cmmi10', 0xbf),
+    '\\theta'                    : ('cmmi10', 0xb5),
+    '\\triangleleft'             : ('cmmi10', 0x2f),
+    '\\triangleright'            : ('cmmi10', 0x2e),
+    '\\upsilon'                  : ('cmmi10', 0xc0),
+    '\\varepsilon'               : ('cmmi10', 0x22),
+    '\\varphi'                   : ('cmmi10', 0x27),
+    '\\varrho'                   : ('cmmi10', 0x25),
+    '\\varsigma'                 : ('cmmi10', 0x26),
+    '\\vartheta'                 : ('cmmi10', 0x23),
+    '\\wp'                       : ('cmmi10', 0x7d),
+    '\\xi'                       : ('cmmi10', 0xbb),
+    '\\zeta'                     : ('cmmi10', 0xb3),
+
+    '!'                          : ('cmr10', 0x21),
+    '%'                          : ('cmr10', 0x25),
+    '&'                          : ('cmr10', 0x26),
+    '('                          : ('cmr10', 0x28),
+    ')'                          : ('cmr10', 0x29),
+    '+'                          : ('cmr10', 0x2b),
+    '0'                          : ('cmr10', 0x30),
+    '1'                          : ('cmr10', 0x31),
+    '2'                          : ('cmr10', 0x32),
+    '3'                          : ('cmr10', 0x33),
+    '4'                          : ('cmr10', 0x34),
+    '5'                          : ('cmr10', 0x35),
+    '6'                          : ('cmr10', 0x36),
+    '7'                          : ('cmr10', 0x37),
+    '8'                          : ('cmr10', 0x38),
+    '9'                          : ('cmr10', 0x39),
+    ':'                          : ('cmr10', 0x3a),
+    ';'                          : ('cmr10', 0x3b),
+    '='                          : ('cmr10', 0x3d),
+    '?'                          : ('cmr10', 0x3f),
+    '@'                          : ('cmr10', 0x40),
+    '['                          : ('cmr10', 0x5b),
+    '\\#'                        : ('cmr10', 0x23),
+    '\\$'                        : ('cmr10', 0x24),
+    '\\%'                        : ('cmr10', 0x25),
+    '\\Delta'                    : ('cmr10', 0xa2),
+    '\\Gamma'                    : ('cmr10', 0xa1),
+    '\\Lambda'                   : ('cmr10', 0xa4),
+    '\\Omega'                    : ('cmr10', 0xad),
+    '\\Phi'                      : ('cmr10', 0xa9),
+    '\\Pi'                       : ('cmr10', 0xa6),
+    '\\Psi'                      : ('cmr10', 0xaa),
+    '\\Sigma'                    : ('cmr10', 0xa7),
+    '\\Theta'                    : ('cmr10', 0xa3),
+    '\\Upsilon'                  : ('cmr10', 0xa8),
+    '\\Xi'                       : ('cmr10', 0xa5),
+    '\\circumflexaccent'         : ('cmr10', 0x5e),
+    '\\combiningacuteaccent'     : ('cmr10', 0xb6),
+    '\\combiningbreve'           : ('cmr10', 0xb8),
+    '\\combiningdiaeresis'       : ('cmr10', 0xc4),
+    '\\combiningdotabove'        : ('cmr10', 0x5f),
+    '\\combininggraveaccent'     : ('cmr10', 0xb5),
+    '\\combiningoverline'        : ('cmr10', 0xb9),
+    '\\combiningtilde'           : ('cmr10', 0x7e),
+    '\\leftbracket'              : ('cmr10', 0x5b),
+    '\\leftparen'                : ('cmr10', 0x28),
+    '\\rightbracket'             : ('cmr10', 0x5d),
+    '\\rightparen'               : ('cmr10', 0x29),
+    '\\widebar'                  : ('cmr10', 0xb9),
+    ']'                          : ('cmr10', 0x5d),
+
+    '*'                          : ('cmsy10', 0xa4),
+    '-'                          : ('cmsy10', 0xa1),
+    '\\Downarrow'                : ('cmsy10', 0x2b),
+    '\\Im'                       : ('cmsy10', 0x3d),
+    '\\Leftarrow'                : ('cmsy10', 0x28),
+    '\\Leftrightarrow'           : ('cmsy10', 0x2c),
+    '\\P'                        : ('cmsy10', 0x7b),
+    '\\Re'                       : ('cmsy10', 0x3c),
+    '\\Rightarrow'               : ('cmsy10', 0x29),
+    '\\S'                        : ('cmsy10', 0x78),
+    '\\Uparrow'                  : ('cmsy10', 0x2a),
+    '\\Updownarrow'              : ('cmsy10', 0x6d),
+    '\\Vert'                     : ('cmsy10', 0x6b),
+    '\\aleph'                    : ('cmsy10', 0x40),
+    '\\approx'                   : ('cmsy10', 0xbc),
+    '\\ast'                      : ('cmsy10', 0xa4),
+    '\\asymp'                    : ('cmsy10', 0xb3),
+    '\\backslash'                : ('cmsy10', 0x6e),
+    '\\bigcirc'                  : ('cmsy10', 0xb0),
+    '\\bigtriangledown'          : ('cmsy10', 0x35),
+    '\\bigtriangleup'            : ('cmsy10', 0x34),
+    '\\bot'                      : ('cmsy10', 0x3f),
+    '\\bullet'                   : ('cmsy10', 0xb2),
+    '\\cap'                      : ('cmsy10', 0x5c),
+    '\\cdot'                     : ('cmsy10', 0xa2),
+    '\\circ'                     : ('cmsy10', 0xb1),
+    '\\clubsuit'                 : ('cmsy10', 0x7c),
+    '\\cup'                      : ('cmsy10', 0x5b),
+    '\\dag'                      : ('cmsy10', 0x79),
+    '\\dashv'                    : ('cmsy10', 0x61),
+    '\\ddag'                     : ('cmsy10', 0x7a),
+    '\\diamond'                  : ('cmsy10', 0xa6),
+    '\\diamondsuit'              : ('cmsy10', 0x7d),
+    '\\div'                      : ('cmsy10', 0xa5),
+    '\\downarrow'                : ('cmsy10', 0x23),
+    '\\emptyset'                 : ('cmsy10', 0x3b),
+    '\\equiv'                    : ('cmsy10', 0xb4),
+    '\\exists'                   : ('cmsy10', 0x39),
+    '\\forall'                   : ('cmsy10', 0x38),
+    '\\geq'                      : ('cmsy10', 0xb8),
+    '\\gg'                       : ('cmsy10', 0xc0),
+    '\\heartsuit'                : ('cmsy10', 0x7e),
+    '\\in'                       : ('cmsy10', 0x32),
+    '\\infty'                    : ('cmsy10', 0x31),
+    '\\lbrace'                   : ('cmsy10', 0x66),
+    '\\lceil'                    : ('cmsy10', 0x64),
+    '\\leftarrow'                : ('cmsy10', 0xc3),
+    '\\leftrightarrow'           : ('cmsy10', 0x24),
+    '\\leq'                      : ('cmsy10', 0x2219),
+    '\\lfloor'                   : ('cmsy10', 0x62),
+    '\\ll'                       : ('cmsy10', 0xbf),
+    '\\mid'                      : ('cmsy10', 0x6a),
+    '\\mp'                       : ('cmsy10', 0xa8),
+    '\\nabla'                    : ('cmsy10', 0x72),
+    '\\nearrow'                  : ('cmsy10', 0x25),
+    '\\neg'                      : ('cmsy10', 0x3a),
+    '\\ni'                       : ('cmsy10', 0x33),
+    '\\nwarrow'                  : ('cmsy10', 0x2d),
+    '\\odot'                     : ('cmsy10', 0xaf),
+    '\\ominus'                   : ('cmsy10', 0xaa),
+    '\\oplus'                    : ('cmsy10', 0xa9),
+    '\\oslash'                   : ('cmsy10', 0xae),
+    '\\otimes'                   : ('cmsy10', 0xad),
+    '\\pm'                       : ('cmsy10', 0xa7),
+    '\\prec'                     : ('cmsy10', 0xc1),
+    '\\preceq'                   : ('cmsy10', 0xb9),
+    '\\prime'                    : ('cmsy10', 0x30),
+    '\\propto'                   : ('cmsy10', 0x2f),
+    '\\rbrace'                   : ('cmsy10', 0x67),
+    '\\rceil'                    : ('cmsy10', 0x65),
+    '\\rfloor'                   : ('cmsy10', 0x63),
+    '\\rightarrow'               : ('cmsy10', 0x21),
+    '\\searrow'                  : ('cmsy10', 0x26),
+    '\\sim'                      : ('cmsy10', 0xbb),
+    '\\simeq'                    : ('cmsy10', 0x27),
+    '\\slash'                    : ('cmsy10', 0x36),
+    '\\spadesuit'                : ('cmsy10', 0xc4),
+    '\\sqcap'                    : ('cmsy10', 0x75),
+    '\\sqcup'                    : ('cmsy10', 0x74),
+    '\\sqsubseteq'               : ('cmsy10', 0x76),
+    '\\sqsupseteq'               : ('cmsy10', 0x77),
+    '\\subset'                   : ('cmsy10', 0xbd),
+    '\\subseteq'                 : ('cmsy10', 0xb5),
+    '\\succ'                     : ('cmsy10', 0xc2),
+    '\\succeq'                   : ('cmsy10', 0xba),
+    '\\supset'                   : ('cmsy10', 0xbe),
+    '\\supseteq'                 : ('cmsy10', 0xb6),
+    '\\swarrow'                  : ('cmsy10', 0x2e),
+    '\\times'                    : ('cmsy10', 0xa3),
+    '\\to'                       : ('cmsy10', 0x21),
+    '\\top'                      : ('cmsy10', 0x3e),
+    '\\uparrow'                  : ('cmsy10', 0x22),
+    '\\updownarrow'              : ('cmsy10', 0x6c),
+    '\\uplus'                    : ('cmsy10', 0x5d),
+    '\\vdash'                    : ('cmsy10', 0x60),
+    '\\vee'                      : ('cmsy10', 0x5f),
+    '\\vert'                     : ('cmsy10', 0x6a),
+    '\\wedge'                    : ('cmsy10', 0x5e),
+    '\\wr'                       : ('cmsy10', 0x6f),
+    '\\|'                        : ('cmsy10', 0x6b),
+    '|'                          : ('cmsy10', 0x6a),
+
+    '\\_'                        : ('cmtt10', 0x5f)
+}
+
+latex_to_cmex = {
+    r'\__sqrt__'   : 112,
+    r'\bigcap'     : 92,
+    r'\bigcup'     : 91,
+    r'\bigodot'    : 75,
+    r'\bigoplus'   : 77,
+    r'\bigotimes'  : 79,
+    r'\biguplus'   : 93,
+    r'\bigvee'     : 95,
+    r'\bigwedge'   : 94,
+    r'\coprod'     : 97,
+    r'\int'        : 90,
+    r'\leftangle'  : 173,
+    r'\leftbrace'  : 169,
+    r'\oint'       : 73,
+    r'\prod'       : 89,
+    r'\rightangle' : 174,
+    r'\rightbrace' : 170,
+    r'\sum'        : 88,
+    r'\widehat'    : 98,
+    r'\widetilde'  : 101,
+}
+
+latex_to_standard = {
+    r'\cong'                     : ('psyr', 64),
+    r'\Delta'                    : ('psyr', 68),
+    r'\Phi'                      : ('psyr', 70),
+    r'\Gamma'                    : ('psyr', 89),
+    r'\alpha'                    : ('psyr', 97),
+    r'\beta'                     : ('psyr', 98),
+    r'\chi'                      : ('psyr', 99),
+    r'\delta'                    : ('psyr', 100),
+    r'\varepsilon'               : ('psyr', 101),
+    r'\phi'                      : ('psyr', 102),
+    r'\gamma'                    : ('psyr', 103),
+    r'\eta'                      : ('psyr', 104),
+    r'\iota'                     : ('psyr', 105),
+    r'\varpsi'                   : ('psyr', 106),
+    r'\kappa'                    : ('psyr', 108),
+    r'\nu'                       : ('psyr', 110),
+    r'\pi'                       : ('psyr', 112),
+    r'\theta'                    : ('psyr', 113),
+    r'\rho'                      : ('psyr', 114),
+    r'\sigma'                    : ('psyr', 115),
+    r'\tau'                      : ('psyr', 116),
+    r'\upsilon'                  : ('psyr', 117),
+    r'\varpi'                    : ('psyr', 118),
+    r'\omega'                    : ('psyr', 119),
+    r'\xi'                       : ('psyr', 120),
+    r'\psi'                      : ('psyr', 121),
+    r'\zeta'                     : ('psyr', 122),
+    r'\sim'                      : ('psyr', 126),
+    r'\leq'                      : ('psyr', 163),
+    r'\infty'                    : ('psyr', 165),
+    r'\clubsuit'                 : ('psyr', 167),
+    r'\diamondsuit'              : ('psyr', 168),
+    r'\heartsuit'                : ('psyr', 169),
+    r'\spadesuit'                : ('psyr', 170),
+    r'\leftrightarrow'           : ('psyr', 171),
+    r'\leftarrow'                : ('psyr', 172),
+    r'\uparrow'                  : ('psyr', 173),
+    r'\rightarrow'               : ('psyr', 174),
+    r'\downarrow'                : ('psyr', 175),
+    r'\pm'                       : ('psyr', 176),
+    r'\geq'                      : ('psyr', 179),
+    r'\times'                    : ('psyr', 180),
+    r'\propto'                   : ('psyr', 181),
+    r'\partial'                  : ('psyr', 182),
+    r'\bullet'                   : ('psyr', 183),
+    r'\div'                      : ('psyr', 184),
+    r'\neq'                      : ('psyr', 185),
+    r'\equiv'                    : ('psyr', 186),
+    r'\approx'                   : ('psyr', 187),
+    r'\ldots'                    : ('psyr', 188),
+    r'\aleph'                    : ('psyr', 192),
+    r'\Im'                       : ('psyr', 193),
+    r'\Re'                       : ('psyr', 194),
+    r'\wp'                       : ('psyr', 195),
+    r'\otimes'                   : ('psyr', 196),
+    r'\oplus'                    : ('psyr', 197),
+    r'\oslash'                   : ('psyr', 198),
+    r'\cap'                      : ('psyr', 199),
+    r'\cup'                      : ('psyr', 200),
+    r'\supset'                   : ('psyr', 201),
+    r'\supseteq'                 : ('psyr', 202),
+    r'\subset'                   : ('psyr', 204),
+    r'\subseteq'                 : ('psyr', 205),
+    r'\in'                       : ('psyr', 206),
+    r'\notin'                    : ('psyr', 207),
+    r'\angle'                    : ('psyr', 208),
+    r'\nabla'                    : ('psyr', 209),
+    r'\textregistered'           : ('psyr', 210),
+    r'\copyright'                : ('psyr', 211),
+    r'\texttrademark'            : ('psyr', 212),
+    r'\Pi'                       : ('psyr', 213),
+    r'\prod'                     : ('psyr', 213),
+    r'\surd'                     : ('psyr', 214),
+    r'\__sqrt__'                 : ('psyr', 214),
+    r'\cdot'                     : ('psyr', 215),
+    r'\urcorner'                 : ('psyr', 216),
+    r'\vee'                      : ('psyr', 217),
+    r'\wedge'                    : ('psyr', 218),
+    r'\Leftrightarrow'           : ('psyr', 219),
+    r'\Leftarrow'                : ('psyr', 220),
+    r'\Uparrow'                  : ('psyr', 221),
+    r'\Rightarrow'               : ('psyr', 222),
+    r'\Downarrow'                : ('psyr', 223),
+    r'\Diamond'                  : ('psyr', 224),
+    r'\Sigma'                    : ('psyr', 229),
+    r'\sum'                      : ('psyr', 229),
+    r'\forall'                   : ('psyr',  34),
+    r'\exists'                   : ('psyr',  36),
+    r'\lceil'                    : ('psyr', 233),
+    r'\lbrace'                   : ('psyr', 123),
+    r'\Psi'                      : ('psyr',  89),
+    r'\bot'                      : ('psyr', 0o136),
+    r'\Omega'                    : ('psyr', 0o127),
+    r'\leftbracket'              : ('psyr', 0o133),
+    r'\rightbracket'             : ('psyr', 0o135),
+    r'\leftbrace'                : ('psyr', 123),
+    r'\leftparen'                : ('psyr', 0o50),
+    r'\prime'                    : ('psyr', 0o242),
+    r'\sharp'                    : ('psyr', 0o43),
+    r'\slash'                    : ('psyr', 0o57),
+    r'\Lamda'                    : ('psyr', 0o114),
+    r'\neg'                      : ('psyr', 0o330),
+    r'\Upsilon'                  : ('psyr', 0o241),
+    r'\rightbrace'               : ('psyr', 0o175),
+    r'\rfloor'                   : ('psyr', 0o373),
+    r'\lambda'                   : ('psyr', 0o154),
+    r'\to'                       : ('psyr', 0o256),
+    r'\Xi'                       : ('psyr', 0o130),
+    r'\emptyset'                 : ('psyr', 0o306),
+    r'\lfloor'                   : ('psyr', 0o353),
+    r'\rightparen'               : ('psyr', 0o51),
+    r'\rceil'                    : ('psyr', 0o371),
+    r'\ni'                       : ('psyr', 0o47),
+    r'\epsilon'                  : ('psyr', 0o145),
+    r'\Theta'                    : ('psyr', 0o121),
+    r'\langle'                   : ('psyr', 0o341),
+    r'\leftangle'                : ('psyr', 0o341),
+    r'\rangle'                   : ('psyr', 0o361),
+    r'\rightangle'               : ('psyr', 0o361),
+    r'\rbrace'                   : ('psyr', 0o175),
+    r'\circ'                     : ('psyr', 0o260),
+    r'\diamond'                  : ('psyr', 0o340),
+    r'\mu'                       : ('psyr', 0o155),
+    r'\mid'                      : ('psyr', 0o352),
+    r'\imath'                    : ('pncri8a', 105),
+    r'\%'                        : ('pncr8a',  37),
+    r'\$'                        : ('pncr8a',  36),
+    r'\{'                        : ('pncr8a', 123),
+    r'\}'                        : ('pncr8a', 125),
+    r'\backslash'                : ('pncr8a',  92),
+    r'\ast'                      : ('pncr8a',  42),
+    r'\#'                        : ('pncr8a',  35),
+
+    r'\circumflexaccent'         : ('pncri8a',   124), # for \hat
+    r'\combiningbreve'           : ('pncri8a',   81),  # for \breve
+    r'\combininggraveaccent'     : ('pncri8a', 114), # for \grave
+    r'\combiningacuteaccent'     : ('pncri8a', 63), # for \accute
+    r'\combiningdiaeresis'       : ('pncri8a', 91), # for \ddot
+    r'\combiningtilde'           : ('pncri8a', 75), # for \tilde
+    r'\combiningrightarrowabove' : ('pncri8a', 110), # for \vec
+    r'\combiningdotabove'        : ('pncri8a', 26), # for \dot
+}
+
+# Automatically generated.
+
+type12uni = {
+    'uni24C8'        : 9416,
+    'aring'          : 229,
+    'uni22A0'        : 8864,
+    'uni2292'        : 8850,
+    'quotedblright'  : 8221,
+    'uni03D2'        : 978,
+    'uni2215'        : 8725,
+    'uni03D0'        : 976,
+    'V'              : 86,
+    'dollar'         : 36,
+    'uni301E'        : 12318,
+    'uni03D5'        : 981,
+    'four'           : 52,
+    'uni25A0'        : 9632,
+    'uni013C'        : 316,
+    'uni013B'        : 315,
+    'uni013E'        : 318,
+    'Yacute'         : 221,
+    'uni25DE'        : 9694,
+    'uni013F'        : 319,
+    'uni255A'        : 9562,
+    'uni2606'        : 9734,
+    'uni0180'        : 384,
+    'uni22B7'        : 8887,
+    'uni044F'        : 1103,
+    'uni22B5'        : 8885,
+    'uni22B4'        : 8884,
+    'uni22AE'        : 8878,
+    'uni22B2'        : 8882,
+    'uni22B1'        : 8881,
+    'uni22B0'        : 8880,
+    'uni25CD'        : 9677,
+    'uni03CE'        : 974,
+    'uni03CD'        : 973,
+    'uni03CC'        : 972,
+    'uni03CB'        : 971,
+    'uni03CA'        : 970,
+    'uni22B8'        : 8888,
+    'uni22C9'        : 8905,
+    'uni0449'        : 1097,
+    'uni20DD'        : 8413,
+    'uni20DC'        : 8412,
+    'uni20DB'        : 8411,
+    'uni2231'        : 8753,
+    'uni25CF'        : 9679,
+    'uni306E'        : 12398,
+    'uni03D1'        : 977,
+    'uni01A1'        : 417,
+    'uni20D7'        : 8407,
+    'uni03D6'        : 982,
+    'uni2233'        : 8755,
+    'uni20D2'        : 8402,
+    'uni20D1'        : 8401,
+    'uni20D0'        : 8400,
+    'P'              : 80,
+    'uni22BE'        : 8894,
+    'uni22BD'        : 8893,
+    'uni22BC'        : 8892,
+    'uni22BB'        : 8891,
+    'underscore'     : 95,
+    'uni03C8'        : 968,
+    'uni03C7'        : 967,
+    'uni0328'        : 808,
+    'uni03C5'        : 965,
+    'uni03C4'        : 964,
+    'uni03C3'        : 963,
+    'uni03C2'        : 962,
+    'uni03C1'        : 961,
+    'uni03C0'        : 960,
+    'uni2010'        : 8208,
+    'uni0130'        : 304,
+    'uni0133'        : 307,
+    'uni0132'        : 306,
+    'uni0135'        : 309,
+    'uni0134'        : 308,
+    'uni0137'        : 311,
+    'uni0136'        : 310,
+    'uni0139'        : 313,
+    'uni0138'        : 312,
+    'uni2244'        : 8772,
+    'uni229A'        : 8858,
+    'uni2571'        : 9585,
+    'uni0278'        : 632,
+    'uni2239'        : 8761,
+    'p'              : 112,
+    'uni3019'        : 12313,
+    'uni25CB'        : 9675,
+    'uni03DB'        : 987,
+    'uni03DC'        : 988,
+    'uni03DA'        : 986,
+    'uni03DF'        : 991,
+    'uni03DD'        : 989,
+    'uni013D'        : 317,
+    'uni220A'        : 8714,
+    'uni220C'        : 8716,
+    'uni220B'        : 8715,
+    'uni220E'        : 8718,
+    'uni220D'        : 8717,
+    'uni220F'        : 8719,
+    'uni22CC'        : 8908,
+    'Otilde'         : 213,
+    'uni25E5'        : 9701,
+    'uni2736'        : 10038,
+    'perthousand'    : 8240,
+    'zero'           : 48,
+    'uni279B'        : 10139,
+    'dotlessi'       : 305,
+    'uni2279'        : 8825,
+    'Scaron'         : 352,
+    'zcaron'         : 382,
+    'uni21D8'        : 8664,
+    'egrave'         : 232,
+    'uni0271'        : 625,
+    'uni01AA'        : 426,
+    'uni2332'        : 9010,
+    'section'        : 167,
+    'uni25E4'        : 9700,
+    'Icircumflex'    : 206,
+    'ntilde'         : 241,
+    'uni041E'        : 1054,
+    'ampersand'      : 38,
+    'uni041C'        : 1052,
+    'uni041A'        : 1050,
+    'uni22AB'        : 8875,
+    'uni21DB'        : 8667,
+    'dotaccent'      : 729,
+    'uni0416'        : 1046,
+    'uni0417'        : 1047,
+    'uni0414'        : 1044,
+    'uni0415'        : 1045,
+    'uni0412'        : 1042,
+    'uni0413'        : 1043,
+    'degree'         : 176,
+    'uni0411'        : 1041,
+    'K'              : 75,
+    'uni25EB'        : 9707,
+    'uni25EF'        : 9711,
+    'uni0418'        : 1048,
+    'uni0419'        : 1049,
+    'uni2263'        : 8803,
+    'uni226E'        : 8814,
+    'uni2251'        : 8785,
+    'uni02C8'        : 712,
+    'uni2262'        : 8802,
+    'acircumflex'    : 226,
+    'uni22B3'        : 8883,
+    'uni2261'        : 8801,
+    'uni2394'        : 9108,
+    'Aring'          : 197,
+    'uni2260'        : 8800,
+    'uni2254'        : 8788,
+    'uni0436'        : 1078,
+    'uni2267'        : 8807,
+    'k'              : 107,
+    'uni22C8'        : 8904,
+    'uni226A'        : 8810,
+    'uni231F'        : 8991,
+    'smalltilde'     : 732,
+    'uni2201'        : 8705,
+    'uni2200'        : 8704,
+    'uni2203'        : 8707,
+    'uni02BD'        : 701,
+    'uni2205'        : 8709,
+    'uni2204'        : 8708,
+    'Agrave'         : 192,
+    'uni2206'        : 8710,
+    'uni2209'        : 8713,
+    'uni2208'        : 8712,
+    'uni226D'        : 8813,
+    'uni2264'        : 8804,
+    'uni263D'        : 9789,
+    'uni2258'        : 8792,
+    'uni02D3'        : 723,
+    'uni02D2'        : 722,
+    'uni02D1'        : 721,
+    'uni02D0'        : 720,
+    'uni25E1'        : 9697,
+    'divide'         : 247,
+    'uni02D5'        : 725,
+    'uni02D4'        : 724,
+    'ocircumflex'    : 244,
+    'uni2524'        : 9508,
+    'uni043A'        : 1082,
+    'uni24CC'        : 9420,
+    'asciitilde'     : 126,
+    'uni22B9'        : 8889,
+    'uni24D2'        : 9426,
+    'uni211E'        : 8478,
+    'uni211D'        : 8477,
+    'uni24DD'        : 9437,
+    'uni211A'        : 8474,
+    'uni211C'        : 8476,
+    'uni211B'        : 8475,
+    'uni25C6'        : 9670,
+    'uni017F'        : 383,
+    'uni017A'        : 378,
+    'uni017C'        : 380,
+    'uni017B'        : 379,
+    'uni0346'        : 838,
+    'uni22F1'        : 8945,
+    'uni22F0'        : 8944,
+    'two'            : 50,
+    'uni2298'        : 8856,
+    'uni24D1'        : 9425,
+    'E'              : 69,
+    'uni025D'        : 605,
+    'scaron'         : 353,
+    'uni2322'        : 8994,
+    'uni25E3'        : 9699,
+    'uni22BF'        : 8895,
+    'F'              : 70,
+    'uni0440'        : 1088,
+    'uni255E'        : 9566,
+    'uni22BA'        : 8890,
+    'uni0175'        : 373,
+    'uni0174'        : 372,
+    'uni0177'        : 375,
+    'uni0176'        : 374,
+    'bracketleft'    : 91,
+    'uni0170'        : 368,
+    'uni0173'        : 371,
+    'uni0172'        : 370,
+    'asciicircum'    : 94,
+    'uni0179'        : 377,
+    'uni2590'        : 9616,
+    'uni25E2'        : 9698,
+    'uni2119'        : 8473,
+    'uni2118'        : 8472,
+    'uni25CC'        : 9676,
+    'f'              : 102,
+    'ordmasculine'   : 186,
+    'uni229B'        : 8859,
+    'uni22A1'        : 8865,
+    'uni2111'        : 8465,
+    'uni2110'        : 8464,
+    'uni2113'        : 8467,
+    'uni2112'        : 8466,
+    'mu'             : 181,
+    'uni2281'        : 8833,
+    'paragraph'      : 182,
+    'nine'           : 57,
+    'uni25EC'        : 9708,
+    'v'              : 118,
+    'uni040C'        : 1036,
+    'uni0113'        : 275,
+    'uni22D0'        : 8912,
+    'uni21CC'        : 8652,
+    'uni21CB'        : 8651,
+    'uni21CA'        : 8650,
+    'uni22A5'        : 8869,
+    'uni21CF'        : 8655,
+    'uni21CE'        : 8654,
+    'uni21CD'        : 8653,
+    'guilsinglleft'  : 8249,
+    'backslash'      : 92,
+    'uni2284'        : 8836,
+    'uni224E'        : 8782,
+    'uni224D'        : 8781,
+    'uni224F'        : 8783,
+    'uni224A'        : 8778,
+    'uni2287'        : 8839,
+    'uni224C'        : 8780,
+    'uni224B'        : 8779,
+    'uni21BD'        : 8637,
+    'uni2286'        : 8838,
+    'uni030F'        : 783,
+    'uni030D'        : 781,
+    'uni030E'        : 782,
+    'uni030B'        : 779,
+    'uni030C'        : 780,
+    'uni030A'        : 778,
+    'uni026E'        : 622,
+    'uni026D'        : 621,
+    'six'            : 54,
+    'uni026A'        : 618,
+    'uni026C'        : 620,
+    'uni25C1'        : 9665,
+    'uni20D6'        : 8406,
+    'uni045B'        : 1115,
+    'uni045C'        : 1116,
+    'uni256B'        : 9579,
+    'uni045A'        : 1114,
+    'uni045F'        : 1119,
+    'uni045E'        : 1118,
+    'A'              : 65,
+    'uni2569'        : 9577,
+    'uni0458'        : 1112,
+    'uni0459'        : 1113,
+    'uni0452'        : 1106,
+    'uni0453'        : 1107,
+    'uni2562'        : 9570,
+    'uni0451'        : 1105,
+    'uni0456'        : 1110,
+    'uni0457'        : 1111,
+    'uni0454'        : 1108,
+    'uni0455'        : 1109,
+    'icircumflex'    : 238,
+    'uni0307'        : 775,
+    'uni0304'        : 772,
+    'uni0305'        : 773,
+    'uni0269'        : 617,
+    'uni0268'        : 616,
+    'uni0300'        : 768,
+    'uni0301'        : 769,
+    'uni0265'        : 613,
+    'uni0264'        : 612,
+    'uni0267'        : 615,
+    'uni0266'        : 614,
+    'uni0261'        : 609,
+    'uni0260'        : 608,
+    'uni0263'        : 611,
+    'uni0262'        : 610,
+    'a'              : 97,
+    'uni2207'        : 8711,
+    'uni2247'        : 8775,
+    'uni2246'        : 8774,
+    'uni2241'        : 8769,
+    'uni2240'        : 8768,
+    'uni2243'        : 8771,
+    'uni2242'        : 8770,
+    'uni2312'        : 8978,
+    'ogonek'         : 731,
+    'uni2249'        : 8777,
+    'uni2248'        : 8776,
+    'uni3030'        : 12336,
+    'q'              : 113,
+    'uni21C2'        : 8642,
+    'uni21C1'        : 8641,
+    'uni21C0'        : 8640,
+    'uni21C7'        : 8647,
+    'uni21C6'        : 8646,
+    'uni21C5'        : 8645,
+    'uni21C4'        : 8644,
+    'uni225F'        : 8799,
+    'uni212C'        : 8492,
+    'uni21C8'        : 8648,
+    'uni2467'        : 9319,
+    'oacute'         : 243,
+    'uni028F'        : 655,
+    'uni028E'        : 654,
+    'uni026F'        : 623,
+    'uni028C'        : 652,
+    'uni028B'        : 651,
+    'uni028A'        : 650,
+    'uni2510'        : 9488,
+    'ograve'         : 242,
+    'edieresis'      : 235,
+    'uni22CE'        : 8910,
+    'uni22CF'        : 8911,
+    'uni219F'        : 8607,
+    'comma'          : 44,
+    'uni22CA'        : 8906,
+    'uni0429'        : 1065,
+    'uni03C6'        : 966,
+    'uni0427'        : 1063,
+    'uni0426'        : 1062,
+    'uni0425'        : 1061,
+    'uni0424'        : 1060,
+    'uni0423'        : 1059,
+    'uni0422'        : 1058,
+    'uni0421'        : 1057,
+    'uni0420'        : 1056,
+    'uni2465'        : 9317,
+    'uni24D0'        : 9424,
+    'uni2464'        : 9316,
+    'uni0430'        : 1072,
+    'otilde'         : 245,
+    'uni2661'        : 9825,
+    'uni24D6'        : 9430,
+    'uni2466'        : 9318,
+    'uni24D5'        : 9429,
+    'uni219A'        : 8602,
+    'uni2518'        : 9496,
+    'uni22B6'        : 8886,
+    'uni2461'        : 9313,
+    'uni24D4'        : 9428,
+    'uni2460'        : 9312,
+    'uni24EA'        : 9450,
+    'guillemotright' : 187,
+    'ecircumflex'    : 234,
+    'greater'        : 62,
+    'uni2011'        : 8209,
+    'uacute'         : 250,
+    'uni2462'        : 9314,
+    'L'              : 76,
+    'bullet'         : 8226,
+    'uni02A4'        : 676,
+    'uni02A7'        : 679,
+    'cedilla'        : 184,
+    'uni02A2'        : 674,
+    'uni2015'        : 8213,
+    'uni22C4'        : 8900,
+    'uni22C5'        : 8901,
+    'uni22AD'        : 8877,
+    'uni22C7'        : 8903,
+    'uni22C0'        : 8896,
+    'uni2016'        : 8214,
+    'uni22C2'        : 8898,
+    'uni22C3'        : 8899,
+    'uni24CF'        : 9423,
+    'uni042F'        : 1071,
+    'uni042E'        : 1070,
+    'uni042D'        : 1069,
+    'ydieresis'      : 255,
+    'l'              : 108,
+    'logicalnot'     : 172,
+    'uni24CA'        : 9418,
+    'uni0287'        : 647,
+    'uni0286'        : 646,
+    'uni0285'        : 645,
+    'uni0284'        : 644,
+    'uni0283'        : 643,
+    'uni0282'        : 642,
+    'uni0281'        : 641,
+    'uni027C'        : 636,
+    'uni2664'        : 9828,
+    'exclamdown'     : 161,
+    'uni25C4'        : 9668,
+    'uni0289'        : 649,
+    'uni0288'        : 648,
+    'uni039A'        : 922,
+    'endash'         : 8211,
+    'uni2640'        : 9792,
+    'uni20E4'        : 8420,
+    'uni0473'        : 1139,
+    'uni20E1'        : 8417,
+    'uni2642'        : 9794,
+    'uni03B8'        : 952,
+    'uni03B9'        : 953,
+    'agrave'         : 224,
+    'uni03B4'        : 948,
+    'uni03B5'        : 949,
+    'uni03B6'        : 950,
+    'uni03B7'        : 951,
+    'uni03B0'        : 944,
+    'uni03B1'        : 945,
+    'uni03B2'        : 946,
+    'uni03B3'        : 947,
+    'uni2555'        : 9557,
+    'Adieresis'      : 196,
+    'germandbls'     : 223,
+    'Odieresis'      : 214,
+    'space'          : 32,
+    'uni0126'        : 294,
+    'uni0127'        : 295,
+    'uni0124'        : 292,
+    'uni0125'        : 293,
+    'uni0122'        : 290,
+    'uni0123'        : 291,
+    'uni0120'        : 288,
+    'uni0121'        : 289,
+    'quoteright'     : 8217,
+    'uni2560'        : 9568,
+    'uni2556'        : 9558,
+    'ucircumflex'    : 251,
+    'uni2561'        : 9569,
+    'uni2551'        : 9553,
+    'uni25B2'        : 9650,
+    'uni2550'        : 9552,
+    'uni2563'        : 9571,
+    'uni2553'        : 9555,
+    'G'              : 71,
+    'uni2564'        : 9572,
+    'uni2552'        : 9554,
+    'quoteleft'      : 8216,
+    'uni2565'        : 9573,
+    'uni2572'        : 9586,
+    'uni2568'        : 9576,
+    'uni2566'        : 9574,
+    'W'              : 87,
+    'uni214A'        : 8522,
+    'uni012F'        : 303,
+    'uni012D'        : 301,
+    'uni012E'        : 302,
+    'uni012B'        : 299,
+    'uni012C'        : 300,
+    'uni255C'        : 9564,
+    'uni012A'        : 298,
+    'uni2289'        : 8841,
+    'Q'              : 81,
+    'uni2320'        : 8992,
+    'uni2321'        : 8993,
+    'g'              : 103,
+    'uni03BD'        : 957,
+    'uni03BE'        : 958,
+    'uni03BF'        : 959,
+    'uni2282'        : 8834,
+    'uni2285'        : 8837,
+    'uni03BA'        : 954,
+    'uni03BB'        : 955,
+    'uni03BC'        : 956,
+    'uni2128'        : 8488,
+    'uni25B7'        : 9655,
+    'w'              : 119,
+    'uni0302'        : 770,
+    'uni03DE'        : 990,
+    'uni25DA'        : 9690,
+    'uni0303'        : 771,
+    'uni0463'        : 1123,
+    'uni0462'        : 1122,
+    'uni3018'        : 12312,
+    'uni2514'        : 9492,
+    'question'       : 63,
+    'uni25B3'        : 9651,
+    'uni24E1'        : 9441,
+    'one'            : 49,
+    'uni200A'        : 8202,
+    'uni2278'        : 8824,
+    'ring'           : 730,
+    'uni0195'        : 405,
+    'figuredash'     : 8210,
+    'uni22EC'        : 8940,
+    'uni0339'        : 825,
+    'uni0338'        : 824,
+    'uni0337'        : 823,
+    'uni0336'        : 822,
+    'uni0335'        : 821,
+    'uni0333'        : 819,
+    'uni0332'        : 818,
+    'uni0331'        : 817,
+    'uni0330'        : 816,
+    'uni01C1'        : 449,
+    'uni01C0'        : 448,
+    'uni01C3'        : 451,
+    'uni01C2'        : 450,
+    'uni2353'        : 9043,
+    'uni0308'        : 776,
+    'uni2218'        : 8728,
+    'uni2219'        : 8729,
+    'uni2216'        : 8726,
+    'uni2217'        : 8727,
+    'uni2214'        : 8724,
+    'uni0309'        : 777,
+    'uni2609'        : 9737,
+    'uni2213'        : 8723,
+    'uni2210'        : 8720,
+    'uni2211'        : 8721,
+    'uni2245'        : 8773,
+    'B'              : 66,
+    'uni25D6'        : 9686,
+    'iacute'         : 237,
+    'uni02E6'        : 742,
+    'uni02E7'        : 743,
+    'uni02E8'        : 744,
+    'uni02E9'        : 745,
+    'uni221D'        : 8733,
+    'uni221E'        : 8734,
+    'Ydieresis'      : 376,
+    'uni221C'        : 8732,
+    'uni22D7'        : 8919,
+    'uni221A'        : 8730,
+    'R'              : 82,
+    'uni24DC'        : 9436,
+    'uni033F'        : 831,
+    'uni033E'        : 830,
+    'uni033C'        : 828,
+    'uni033B'        : 827,
+    'uni033A'        : 826,
+    'b'              : 98,
+    'uni228A'        : 8842,
+    'uni22DB'        : 8923,
+    'uni2554'        : 9556,
+    'uni046B'        : 1131,
+    'uni046A'        : 1130,
+    'r'              : 114,
+    'uni24DB'        : 9435,
+    'Ccedilla'       : 199,
+    'minus'          : 8722,
+    'uni24DA'        : 9434,
+    'uni03F0'        : 1008,
+    'uni03F1'        : 1009,
+    'uni20AC'        : 8364,
+    'uni2276'        : 8822,
+    'uni24C0'        : 9408,
+    'uni0162'        : 354,
+    'uni0163'        : 355,
+    'uni011E'        : 286,
+    'uni011D'        : 285,
+    'uni011C'        : 284,
+    'uni011B'        : 283,
+    'uni0164'        : 356,
+    'uni0165'        : 357,
+    'Lslash'         : 321,
+    'uni0168'        : 360,
+    'uni0169'        : 361,
+    'uni25C9'        : 9673,
+    'uni02E5'        : 741,
+    'uni21C3'        : 8643,
+    'uni24C4'        : 9412,
+    'uni24E2'        : 9442,
+    'uni2277'        : 8823,
+    'uni013A'        : 314,
+    'uni2102'        : 8450,
+    'Uacute'         : 218,
+    'uni2317'        : 8983,
+    'uni2107'        : 8455,
+    'uni221F'        : 8735,
+    'yacute'         : 253,
+    'uni3012'        : 12306,
+    'Ucircumflex'    : 219,
+    'uni015D'        : 349,
+    'quotedbl'       : 34,
+    'uni25D9'        : 9689,
+    'uni2280'        : 8832,
+    'uni22AF'        : 8879,
+    'onehalf'        : 189,
+    'uni221B'        : 8731,
+    'Thorn'          : 222,
+    'uni2226'        : 8742,
+    'M'              : 77,
+    'uni25BA'        : 9658,
+    'uni2463'        : 9315,
+    'uni2336'        : 9014,
+    'eight'          : 56,
+    'uni2236'        : 8758,
+    'multiply'       : 215,
+    'uni210C'        : 8460,
+    'uni210A'        : 8458,
+    'uni21C9'        : 8649,
+    'grave'          : 96,
+    'uni210E'        : 8462,
+    'uni0117'        : 279,
+    'uni016C'        : 364,
+    'uni0115'        : 277,
+    'uni016A'        : 362,
+    'uni016F'        : 367,
+    'uni0112'        : 274,
+    'uni016D'        : 365,
+    'uni016E'        : 366,
+    'Ocircumflex'    : 212,
+    'uni2305'        : 8965,
+    'm'              : 109,
+    'uni24DF'        : 9439,
+    'uni0119'        : 281,
+    'uni0118'        : 280,
+    'uni20A3'        : 8355,
+    'uni20A4'        : 8356,
+    'uni20A7'        : 8359,
+    'uni2288'        : 8840,
+    'uni24C3'        : 9411,
+    'uni251C'        : 9500,
+    'uni228D'        : 8845,
+    'uni222F'        : 8751,
+    'uni222E'        : 8750,
+    'uni222D'        : 8749,
+    'uni222C'        : 8748,
+    'uni222B'        : 8747,
+    'uni222A'        : 8746,
+    'uni255B'        : 9563,
+    'Ugrave'         : 217,
+    'uni24DE'        : 9438,
+    'guilsinglright' : 8250,
+    'uni250A'        : 9482,
+    'Ntilde'         : 209,
+    'uni0279'        : 633,
+    'questiondown'   : 191,
+    'uni256C'        : 9580,
+    'Atilde'         : 195,
+    'uni0272'        : 626,
+    'uni0273'        : 627,
+    'uni0270'        : 624,
+    'ccedilla'       : 231,
+    'uni0276'        : 630,
+    'uni0277'        : 631,
+    'uni0274'        : 628,
+    'uni0275'        : 629,
+    'uni2252'        : 8786,
+    'uni041F'        : 1055,
+    'uni2250'        : 8784,
+    'Z'              : 90,
+    'uni2256'        : 8790,
+    'uni2257'        : 8791,
+    'copyright'      : 169,
+    'uni2255'        : 8789,
+    'uni043D'        : 1085,
+    'uni043E'        : 1086,
+    'uni043F'        : 1087,
+    'yen'            : 165,
+    'uni041D'        : 1053,
+    'uni043B'        : 1083,
+    'uni043C'        : 1084,
+    'uni21B0'        : 8624,
+    'uni21B1'        : 8625,
+    'uni21B2'        : 8626,
+    'uni21B3'        : 8627,
+    'uni21B4'        : 8628,
+    'uni21B5'        : 8629,
+    'uni21B6'        : 8630,
+    'uni21B7'        : 8631,
+    'uni21B8'        : 8632,
+    'Eacute'         : 201,
+    'uni2311'        : 8977,
+    'uni2310'        : 8976,
+    'uni228F'        : 8847,
+    'uni25DB'        : 9691,
+    'uni21BA'        : 8634,
+    'uni21BB'        : 8635,
+    'uni21BC'        : 8636,
+    'uni2017'        : 8215,
+    'uni21BE'        : 8638,
+    'uni21BF'        : 8639,
+    'uni231C'        : 8988,
+    'H'              : 72,
+    'uni0293'        : 659,
+    'uni2202'        : 8706,
+    'uni22A4'        : 8868,
+    'uni231E'        : 8990,
+    'uni2232'        : 8754,
+    'uni225B'        : 8795,
+    'uni225C'        : 8796,
+    'uni24D9'        : 9433,
+    'uni225A'        : 8794,
+    'uni0438'        : 1080,
+    'uni0439'        : 1081,
+    'uni225D'        : 8797,
+    'uni225E'        : 8798,
+    'uni0434'        : 1076,
+    'X'              : 88,
+    'uni007F'        : 127,
+    'uni0437'        : 1079,
+    'Idieresis'      : 207,
+    'uni0431'        : 1073,
+    'uni0432'        : 1074,
+    'uni0433'        : 1075,
+    'uni22AC'        : 8876,
+    'uni22CD'        : 8909,
+    'uni25A3'        : 9635,
+    'bar'            : 124,
+    'uni24BB'        : 9403,
+    'uni037E'        : 894,
+    'uni027B'        : 635,
+    'h'              : 104,
+    'uni027A'        : 634,
+    'uni027F'        : 639,
+    'uni027D'        : 637,
+    'uni027E'        : 638,
+    'uni2227'        : 8743,
+    'uni2004'        : 8196,
+    'uni2225'        : 8741,
+    'uni2224'        : 8740,
+    'uni2223'        : 8739,
+    'uni2222'        : 8738,
+    'uni2221'        : 8737,
+    'uni2220'        : 8736,
+    'x'              : 120,
+    'uni2323'        : 8995,
+    'uni2559'        : 9561,
+    'uni2558'        : 9560,
+    'uni2229'        : 8745,
+    'uni2228'        : 8744,
+    'udieresis'      : 252,
+    'uni029D'        : 669,
+    'ordfeminine'    : 170,
+    'uni22CB'        : 8907,
+    'uni233D'        : 9021,
+    'uni0428'        : 1064,
+    'uni24C6'        : 9414,
+    'uni22DD'        : 8925,
+    'uni24C7'        : 9415,
+    'uni015C'        : 348,
+    'uni015B'        : 347,
+    'uni015A'        : 346,
+    'uni22AA'        : 8874,
+    'uni015F'        : 351,
+    'uni015E'        : 350,
+    'braceleft'      : 123,
+    'uni24C5'        : 9413,
+    'uni0410'        : 1040,
+    'uni03AA'        : 938,
+    'uni24C2'        : 9410,
+    'uni03AC'        : 940,
+    'uni03AB'        : 939,
+    'macron'         : 175,
+    'uni03AD'        : 941,
+    'uni03AF'        : 943,
+    'uni0294'        : 660,
+    'uni0295'        : 661,
+    'uni0296'        : 662,
+    'uni0297'        : 663,
+    'uni0290'        : 656,
+    'uni0291'        : 657,
+    'uni0292'        : 658,
+    'atilde'         : 227,
+    'Acircumflex'    : 194,
+    'uni2370'        : 9072,
+    'uni24C1'        : 9409,
+    'uni0298'        : 664,
+    'uni0299'        : 665,
+    'Oslash'         : 216,
+    'uni029E'        : 670,
+    'C'              : 67,
+    'quotedblleft'   : 8220,
+    'uni029B'        : 667,
+    'uni029C'        : 668,
+    'uni03A9'        : 937,
+    'uni03A8'        : 936,
+    'S'              : 83,
+    'uni24C9'        : 9417,
+    'uni03A1'        : 929,
+    'uni03A0'        : 928,
+    'exclam'         : 33,
+    'uni03A5'        : 933,
+    'uni03A4'        : 932,
+    'uni03A7'        : 935,
+    'Zcaron'         : 381,
+    'uni2133'        : 8499,
+    'uni2132'        : 8498,
+    'uni0159'        : 345,
+    'uni0158'        : 344,
+    'uni2137'        : 8503,
+    'uni2005'        : 8197,
+    'uni2135'        : 8501,
+    'uni2134'        : 8500,
+    'uni02BA'        : 698,
+    'uni2033'        : 8243,
+    'uni0151'        : 337,
+    'uni0150'        : 336,
+    'uni0157'        : 343,
+    'equal'          : 61,
+    'uni0155'        : 341,
+    'uni0154'        : 340,
+    's'              : 115,
+    'uni233F'        : 9023,
+    'eth'            : 240,
+    'uni24BE'        : 9406,
+    'uni21E9'        : 8681,
+    'uni2060'        : 8288,
+    'Egrave'         : 200,
+    'uni255D'        : 9565,
+    'uni24CD'        : 9421,
+    'uni21E1'        : 8673,
+    'uni21B9'        : 8633,
+    'hyphen'         : 45,
+    'uni01BE'        : 446,
+    'uni01BB'        : 443,
+    'period'         : 46,
+    'igrave'         : 236,
+    'uni01BA'        : 442,
+    'uni2296'        : 8854,
+    'uni2297'        : 8855,
+    'uni2294'        : 8852,
+    'uni2295'        : 8853,
+    'colon'          : 58,
+    'uni2293'        : 8851,
+    'uni2290'        : 8848,
+    'uni2291'        : 8849,
+    'uni032D'        : 813,
+    'uni032E'        : 814,
+    'uni032F'        : 815,
+    'uni032A'        : 810,
+    'uni032B'        : 811,
+    'uni032C'        : 812,
+    'uni231D'        : 8989,
+    'Ecircumflex'    : 202,
+    'uni24D7'        : 9431,
+    'uni25DD'        : 9693,
+    'trademark'      : 8482,
+    'Aacute'         : 193,
+    'cent'           : 162,
+    'uni0445'        : 1093,
+    'uni266E'        : 9838,
+    'uni266D'        : 9837,
+    'uni266B'        : 9835,
+    'uni03C9'        : 969,
+    'uni2003'        : 8195,
+    'uni2047'        : 8263,
+    'lslash'         : 322,
+    'uni03A6'        : 934,
+    'uni2043'        : 8259,
+    'uni250C'        : 9484,
+    'uni2040'        : 8256,
+    'uni255F'        : 9567,
+    'uni24CB'        : 9419,
+    'uni0472'        : 1138,
+    'uni0446'        : 1094,
+    'uni0474'        : 1140,
+    'uni0475'        : 1141,
+    'uni2508'        : 9480,
+    'uni2660'        : 9824,
+    'uni2506'        : 9478,
+    'uni2502'        : 9474,
+    'c'              : 99,
+    'uni2500'        : 9472,
+    'N'              : 78,
+    'uni22A6'        : 8870,
+    'uni21E7'        : 8679,
+    'uni2130'        : 8496,
+    'uni2002'        : 8194,
+    'breve'          : 728,
+    'uni0442'        : 1090,
+    'Oacute'         : 211,
+    'uni229F'        : 8863,
+    'uni25C7'        : 9671,
+    'uni229D'        : 8861,
+    'uni229E'        : 8862,
+    'guillemotleft'  : 171,
+    'uni0329'        : 809,
+    'uni24E5'        : 9445,
+    'uni011F'        : 287,
+    'uni0324'        : 804,
+    'uni0325'        : 805,
+    'uni0326'        : 806,
+    'uni0327'        : 807,
+    'uni0321'        : 801,
+    'uni0322'        : 802,
+    'n'              : 110,
+    'uni2032'        : 8242,
+    'uni2269'        : 8809,
+    'uni2268'        : 8808,
+    'uni0306'        : 774,
+    'uni226B'        : 8811,
+    'uni21EA'        : 8682,
+    'uni0166'        : 358,
+    'uni203B'        : 8251,
+    'uni01B5'        : 437,
+    'idieresis'      : 239,
+    'uni02BC'        : 700,
+    'uni01B0'        : 432,
+    'braceright'     : 125,
+    'seven'          : 55,
+    'uni02BB'        : 699,
+    'uni011A'        : 282,
+    'uni29FB'        : 10747,
+    'brokenbar'      : 166,
+    'uni2036'        : 8246,
+    'uni25C0'        : 9664,
+    'uni0156'        : 342,
+    'uni22D5'        : 8917,
+    'uni0258'        : 600,
+    'ugrave'         : 249,
+    'uni22D6'        : 8918,
+    'uni22D1'        : 8913,
+    'uni2034'        : 8244,
+    'uni22D3'        : 8915,
+    'uni22D2'        : 8914,
+    'uni203C'        : 8252,
+    'uni223E'        : 8766,
+    'uni02BF'        : 703,
+    'uni22D9'        : 8921,
+    'uni22D8'        : 8920,
+    'uni25BD'        : 9661,
+    'uni25BE'        : 9662,
+    'uni25BF'        : 9663,
+    'uni041B'        : 1051,
+    'periodcentered' : 183,
+    'uni25BC'        : 9660,
+    'uni019E'        : 414,
+    'uni019B'        : 411,
+    'uni019A'        : 410,
+    'uni2007'        : 8199,
+    'uni0391'        : 913,
+    'uni0390'        : 912,
+    'uni0393'        : 915,
+    'uni0392'        : 914,
+    'uni0395'        : 917,
+    'uni0394'        : 916,
+    'uni0397'        : 919,
+    'uni0396'        : 918,
+    'uni0399'        : 921,
+    'uni0398'        : 920,
+    'uni25C8'        : 9672,
+    'uni2468'        : 9320,
+    'sterling'       : 163,
+    'uni22EB'        : 8939,
+    'uni039C'        : 924,
+    'uni039B'        : 923,
+    'uni039E'        : 926,
+    'uni039D'        : 925,
+    'uni039F'        : 927,
+    'I'              : 73,
+    'uni03E1'        : 993,
+    'uni03E0'        : 992,
+    'uni2319'        : 8985,
+    'uni228B'        : 8843,
+    'uni25B5'        : 9653,
+    'uni25B6'        : 9654,
+    'uni22EA'        : 8938,
+    'uni24B9'        : 9401,
+    'uni044E'        : 1102,
+    'uni0199'        : 409,
+    'uni2266'        : 8806,
+    'Y'              : 89,
+    'uni22A2'        : 8866,
+    'Eth'            : 208,
+    'uni266F'        : 9839,
+    'emdash'         : 8212,
+    'uni263B'        : 9787,
+    'uni24BD'        : 9405,
+    'uni22DE'        : 8926,
+    'uni0360'        : 864,
+    'uni2557'        : 9559,
+    'uni22DF'        : 8927,
+    'uni22DA'        : 8922,
+    'uni22DC'        : 8924,
+    'uni0361'        : 865,
+    'i'              : 105,
+    'uni24BF'        : 9407,
+    'uni0362'        : 866,
+    'uni263E'        : 9790,
+    'uni028D'        : 653,
+    'uni2259'        : 8793,
+    'uni0323'        : 803,
+    'uni2265'        : 8805,
+    'daggerdbl'      : 8225,
+    'y'              : 121,
+    'uni010A'        : 266,
+    'plusminus'      : 177,
+    'less'           : 60,
+    'uni21AE'        : 8622,
+    'uni0315'        : 789,
+    'uni230B'        : 8971,
+    'uni21AF'        : 8623,
+    'uni21AA'        : 8618,
+    'uni21AC'        : 8620,
+    'uni21AB'        : 8619,
+    'uni01FB'        : 507,
+    'uni01FC'        : 508,
+    'uni223A'        : 8762,
+    'uni01FA'        : 506,
+    'uni01FF'        : 511,
+    'uni01FD'        : 509,
+    'uni01FE'        : 510,
+    'uni2567'        : 9575,
+    'uni25E0'        : 9696,
+    'uni0104'        : 260,
+    'uni0105'        : 261,
+    'uni0106'        : 262,
+    'uni0107'        : 263,
+    'uni0100'        : 256,
+    'uni0101'        : 257,
+    'uni0102'        : 258,
+    'uni0103'        : 259,
+    'uni2038'        : 8248,
+    'uni2009'        : 8201,
+    'uni2008'        : 8200,
+    'uni0108'        : 264,
+    'uni0109'        : 265,
+    'uni02A1'        : 673,
+    'uni223B'        : 8763,
+    'uni226C'        : 8812,
+    'uni25AC'        : 9644,
+    'uni24D3'        : 9427,
+    'uni21E0'        : 8672,
+    'uni21E3'        : 8675,
+    'Udieresis'      : 220,
+    'uni21E2'        : 8674,
+    'D'              : 68,
+    'uni21E5'        : 8677,
+    'uni2621'        : 9761,
+    'uni21D1'        : 8657,
+    'uni203E'        : 8254,
+    'uni22C6'        : 8902,
+    'uni21E4'        : 8676,
+    'uni010D'        : 269,
+    'uni010E'        : 270,
+    'uni010F'        : 271,
+    'five'           : 53,
+    'T'              : 84,
+    'uni010B'        : 267,
+    'uni010C'        : 268,
+    'uni2605'        : 9733,
+    'uni2663'        : 9827,
+    'uni21E6'        : 8678,
+    'uni24B6'        : 9398,
+    'uni22C1'        : 8897,
+    'oslash'         : 248,
+    'acute'          : 180,
+    'uni01F0'        : 496,
+    'd'              : 100,
+    'OE'             : 338,
+    'uni22E3'        : 8931,
+    'Igrave'         : 204,
+    'uni2308'        : 8968,
+    'uni2309'        : 8969,
+    'uni21A9'        : 8617,
+    't'              : 116,
+    'uni2313'        : 8979,
+    'uni03A3'        : 931,
+    'uni21A4'        : 8612,
+    'uni21A7'        : 8615,
+    'uni21A6'        : 8614,
+    'uni21A1'        : 8609,
+    'uni21A0'        : 8608,
+    'uni21A3'        : 8611,
+    'uni21A2'        : 8610,
+    'parenright'     : 41,
+    'uni256A'        : 9578,
+    'uni25DC'        : 9692,
+    'uni24CE'        : 9422,
+    'uni042C'        : 1068,
+    'uni24E0'        : 9440,
+    'uni042B'        : 1067,
+    'uni0409'        : 1033,
+    'uni0408'        : 1032,
+    'uni24E7'        : 9447,
+    'uni25B4'        : 9652,
+    'uni042A'        : 1066,
+    'uni228E'        : 8846,
+    'uni0401'        : 1025,
+    'adieresis'      : 228,
+    'uni0403'        : 1027,
+    'quotesingle'    : 39,
+    'uni0405'        : 1029,
+    'uni0404'        : 1028,
+    'uni0407'        : 1031,
+    'uni0406'        : 1030,
+    'uni229C'        : 8860,
+    'uni2306'        : 8966,
+    'uni2253'        : 8787,
+    'twodotenleader' : 8229,
+    'uni2131'        : 8497,
+    'uni21DA'        : 8666,
+    'uni2234'        : 8756,
+    'uni2235'        : 8757,
+    'uni01A5'        : 421,
+    'uni2237'        : 8759,
+    'uni2230'        : 8752,
+    'uni02CC'        : 716,
+    'slash'          : 47,
+    'uni01A0'        : 416,
+    'ellipsis'       : 8230,
+    'uni2299'        : 8857,
+    'uni2238'        : 8760,
+    'numbersign'     : 35,
+    'uni21A8'        : 8616,
+    'uni223D'        : 8765,
+    'uni01AF'        : 431,
+    'uni223F'        : 8767,
+    'uni01AD'        : 429,
+    'uni01AB'        : 427,
+    'odieresis'      : 246,
+    'uni223C'        : 8764,
+    'uni227D'        : 8829,
+    'uni0280'        : 640,
+    'O'              : 79,
+    'uni227E'        : 8830,
+    'uni21A5'        : 8613,
+    'uni22D4'        : 8916,
+    'uni25D4'        : 9684,
+    'uni227F'        : 8831,
+    'uni0435'        : 1077,
+    'uni2302'        : 8962,
+    'uni2669'        : 9833,
+    'uni24E3'        : 9443,
+    'uni2720'        : 10016,
+    'uni22A8'        : 8872,
+    'uni22A9'        : 8873,
+    'uni040A'        : 1034,
+    'uni22A7'        : 8871,
+    'oe'             : 339,
+    'uni040B'        : 1035,
+    'uni040E'        : 1038,
+    'uni22A3'        : 8867,
+    'o'              : 111,
+    'uni040F'        : 1039,
+    'Edieresis'      : 203,
+    'uni25D5'        : 9685,
+    'plus'           : 43,
+    'uni044D'        : 1101,
+    'uni263C'        : 9788,
+    'uni22E6'        : 8934,
+    'uni2283'        : 8835,
+    'uni258C'        : 9612,
+    'uni219E'        : 8606,
+    'uni24E4'        : 9444,
+    'uni2136'        : 8502,
+    'dagger'         : 8224,
+    'uni24B7'        : 9399,
+    'uni219B'        : 8603,
+    'uni22E5'        : 8933,
+    'three'          : 51,
+    'uni210B'        : 8459,
+    'uni2534'        : 9524,
+    'uni24B8'        : 9400,
+    'uni230A'        : 8970,
+    'hungarumlaut'   : 733,
+    'parenleft'      : 40,
+    'uni0148'        : 328,
+    'uni0149'        : 329,
+    'uni2124'        : 8484,
+    'uni2125'        : 8485,
+    'uni2126'        : 8486,
+    'uni2127'        : 8487,
+    'uni0140'        : 320,
+    'uni2129'        : 8489,
+    'uni25C5'        : 9669,
+    'uni0143'        : 323,
+    'uni0144'        : 324,
+    'uni0145'        : 325,
+    'uni0146'        : 326,
+    'uni0147'        : 327,
+    'uni210D'        : 8461,
+    'fraction'       : 8260,
+    'uni2031'        : 8241,
+    'uni2196'        : 8598,
+    'uni2035'        : 8245,
+    'uni24E6'        : 9446,
+    'uni016B'        : 363,
+    'uni24BA'        : 9402,
+    'uni266A'        : 9834,
+    'uni0116'        : 278,
+    'uni2115'        : 8469,
+    'registered'     : 174,
+    'J'              : 74,
+    'uni25DF'        : 9695,
+    'uni25CE'        : 9678,
+    'uni273D'        : 10045,
+    'dieresis'       : 168,
+    'uni212B'        : 8491,
+    'uni0114'        : 276,
+    'uni212D'        : 8493,
+    'uni212E'        : 8494,
+    'uni212F'        : 8495,
+    'uni014A'        : 330,
+    'uni014B'        : 331,
+    'uni014C'        : 332,
+    'uni014D'        : 333,
+    'uni014E'        : 334,
+    'uni014F'        : 335,
+    'uni025E'        : 606,
+    'uni24E8'        : 9448,
+    'uni0111'        : 273,
+    'uni24E9'        : 9449,
+    'Ograve'         : 210,
+    'j'              : 106,
+    'uni2195'        : 8597,
+    'uni2194'        : 8596,
+    'uni2197'        : 8599,
+    'uni2037'        : 8247,
+    'uni2191'        : 8593,
+    'uni2190'        : 8592,
+    'uni2193'        : 8595,
+    'uni2192'        : 8594,
+    'uni29FA'        : 10746,
+    'uni2713'        : 10003,
+    'z'              : 122,
+    'uni2199'        : 8601,
+    'uni2198'        : 8600,
+    'uni2667'        : 9831,
+    'ae'             : 230,
+    'uni0448'        : 1096,
+    'semicolon'      : 59,
+    'uni2666'        : 9830,
+    'uni038F'        : 911,
+    'uni0444'        : 1092,
+    'uni0447'        : 1095,
+    'uni038E'        : 910,
+    'uni0441'        : 1089,
+    'uni038C'        : 908,
+    'uni0443'        : 1091,
+    'uni038A'        : 906,
+    'uni0250'        : 592,
+    'uni0251'        : 593,
+    'uni0252'        : 594,
+    'uni0253'        : 595,
+    'uni0254'        : 596,
+    'at'             : 64,
+    'uni0256'        : 598,
+    'uni0257'        : 599,
+    'uni0167'        : 359,
+    'uni0259'        : 601,
+    'uni228C'        : 8844,
+    'uni2662'        : 9826,
+    'uni0319'        : 793,
+    'uni0318'        : 792,
+    'uni24BC'        : 9404,
+    'uni0402'        : 1026,
+    'uni22EF'        : 8943,
+    'Iacute'         : 205,
+    'uni22ED'        : 8941,
+    'uni22EE'        : 8942,
+    'uni0311'        : 785,
+    'uni0310'        : 784,
+    'uni21E8'        : 8680,
+    'uni0312'        : 786,
+    'percent'        : 37,
+    'uni0317'        : 791,
+    'uni0316'        : 790,
+    'uni21D6'        : 8662,
+    'uni21D7'        : 8663,
+    'uni21D4'        : 8660,
+    'uni21D5'        : 8661,
+    'uni21D2'        : 8658,
+    'uni21D3'        : 8659,
+    'uni21D0'        : 8656,
+    'uni2138'        : 8504,
+    'uni2270'        : 8816,
+    'uni2271'        : 8817,
+    'uni2272'        : 8818,
+    'uni2273'        : 8819,
+    'uni2274'        : 8820,
+    'uni2275'        : 8821,
+    'bracketright'   : 93,
+    'uni21D9'        : 8665,
+    'uni21DF'        : 8671,
+    'uni21DD'        : 8669,
+    'uni21DE'        : 8670,
+    'AE'             : 198,
+    'uni03AE'        : 942,
+    'uni227A'        : 8826,
+    'uni227B'        : 8827,
+    'uni227C'        : 8828,
+    'asterisk'       : 42,
+    'aacute'         : 225,
+    'uni226F'        : 8815,
+    'uni22E2'        : 8930,
+    'uni0386'        : 902,
+    'uni22E0'        : 8928,
+    'uni22E1'        : 8929,
+    'U'              : 85,
+    'uni22E7'        : 8935,
+    'uni22E4'        : 8932,
+    'uni0387'        : 903,
+    'uni031A'        : 794,
+    'eacute'         : 233,
+    'uni22E8'        : 8936,
+    'uni22E9'        : 8937,
+    'uni24D8'        : 9432,
+    'uni025A'        : 602,
+    'uni025B'        : 603,
+    'uni025C'        : 604,
+    'e'              : 101,
+    'uni0128'        : 296,
+    'uni025F'        : 607,
+    'uni2665'        : 9829,
+    'thorn'          : 254,
+    'uni0129'        : 297,
+    'uni253C'        : 9532,
+    'uni25D7'        : 9687,
+    'u'              : 117,
+    'uni0388'        : 904,
+    'uni0389'        : 905,
+    'uni0255'        : 597,
+    'uni0171'        : 369,
+    'uni0384'        : 900,
+    'uni0385'        : 901,
+    'uni044A'        : 1098,
+    'uni252C'        : 9516,
+    'uni044C'        : 1100,
+    'uni044B'        : 1099
+}
+
+uni2type1 = {v: k for k, v in type12uni.items()}
+
+tex2uni = {
+    'widehat'                  : 0x0302,
+    'widetilde'                : 0x0303,
+    'widebar'                  : 0x0305,
+    'langle'                   : 0x27e8,
+    'rangle'                   : 0x27e9,
+    'perp'                     : 0x27c2,
+    'neq'                      : 0x2260,
+    'Join'                     : 0x2a1d,
+    'leqslant'                 : 0x2a7d,
+    'geqslant'                 : 0x2a7e,
+    'lessapprox'               : 0x2a85,
+    'gtrapprox'                : 0x2a86,
+    'lesseqqgtr'               : 0x2a8b,
+    'gtreqqless'               : 0x2a8c,
+    'triangleeq'               : 0x225c,
+    'eqslantless'              : 0x2a95,
+    'eqslantgtr'               : 0x2a96,
+    'backepsilon'              : 0x03f6,
+    'precapprox'               : 0x2ab7,
+    'succapprox'               : 0x2ab8,
+    'fallingdotseq'            : 0x2252,
+    'subseteqq'                : 0x2ac5,
+    'supseteqq'                : 0x2ac6,
+    'varpropto'                : 0x221d,
+    'precnapprox'              : 0x2ab9,
+    'succnapprox'              : 0x2aba,
+    'subsetneqq'               : 0x2acb,
+    'supsetneqq'               : 0x2acc,
+    'lnapprox'                 : 0x2ab9,
+    'gnapprox'                 : 0x2aba,
+    'longleftarrow'            : 0x27f5,
+    'longrightarrow'           : 0x27f6,
+    'longleftrightarrow'       : 0x27f7,
+    'Longleftarrow'            : 0x27f8,
+    'Longrightarrow'           : 0x27f9,
+    'Longleftrightarrow'       : 0x27fa,
+    'longmapsto'               : 0x27fc,
+    'leadsto'                  : 0x21dd,
+    'dashleftarrow'            : 0x290e,
+    'dashrightarrow'           : 0x290f,
+    'circlearrowleft'          : 0x21ba,
+    'circlearrowright'         : 0x21bb,
+    'leftrightsquigarrow'      : 0x21ad,
+    'leftsquigarrow'           : 0x219c,
+    'rightsquigarrow'          : 0x219d,
+    'Game'                     : 0x2141,
+    'hbar'                     : 0x0127,
+    'hslash'                   : 0x210f,
+    'ldots'                    : 0x2026,
+    'vdots'                    : 0x22ee,
+    'doteqdot'                 : 0x2251,
+    'doteq'                    : 8784,
+    'partial'                  : 8706,
+    'gg'                       : 8811,
+    'asymp'                    : 8781,
+    'blacktriangledown'        : 9662,
+    'otimes'                   : 8855,
+    'nearrow'                  : 8599,
+    'varpi'                    : 982,
+    'vee'                      : 8744,
+    'vec'                      : 8407,
+    'smile'                    : 8995,
+    'succnsim'                 : 8937,
+    'gimel'                    : 8503,
+    'vert'                     : 124,
+    '|'                        : 124,
+    'varrho'                   : 1009,
+    'P'                        : 182,
+    'approxident'              : 8779,
+    'Swarrow'                  : 8665,
+    'textasciicircum'          : 94,
+    'imageof'                  : 8887,
+    'ntriangleleft'            : 8938,
+    'nleq'                     : 8816,
+    'div'                      : 247,
+    'nparallel'                : 8742,
+    'Leftarrow'                : 8656,
+    'lll'                      : 8920,
+    'oiint'                    : 8751,
+    'ngeq'                     : 8817,
+    'Theta'                    : 920,
+    'origof'                   : 8886,
+    'blacksquare'              : 9632,
+    'solbar'                   : 9023,
+    'neg'                      : 172,
+    'sum'                      : 8721,
+    'Vdash'                    : 8873,
+    'coloneq'                  : 8788,
+    'degree'                   : 176,
+    'bowtie'                   : 8904,
+    'blacktriangleright'       : 9654,
+    'varsigma'                 : 962,
+    'leq'                      : 8804,
+    'ggg'                      : 8921,
+    'lneqq'                    : 8808,
+    'scurel'                   : 8881,
+    'stareq'                   : 8795,
+    'BbbN'                     : 8469,
+    'nLeftarrow'               : 8653,
+    'nLeftrightarrow'          : 8654,
+    'k'                        : 808,
+    'bot'                      : 8869,
+    'BbbC'                     : 8450,
+    'Lsh'                      : 8624,
+    'leftleftarrows'           : 8647,
+    'BbbZ'                     : 8484,
+    'digamma'                  : 989,
+    'BbbR'                     : 8477,
+    'BbbP'                     : 8473,
+    'BbbQ'                     : 8474,
+    'vartriangleright'         : 8883,
+    'succsim'                  : 8831,
+    'wedge'                    : 8743,
+    'lessgtr'                  : 8822,
+    'veebar'                   : 8891,
+    'mapsdown'                 : 8615,
+    'Rsh'                      : 8625,
+    'chi'                      : 967,
+    'prec'                     : 8826,
+    'nsubseteq'                : 8840,
+    'therefore'                : 8756,
+    'eqcirc'                   : 8790,
+    'textexclamdown'           : 161,
+    'nRightarrow'              : 8655,
+    'flat'                     : 9837,
+    'notin'                    : 8713,
+    'llcorner'                 : 8990,
+    'varepsilon'               : 949,
+    'bigtriangleup'            : 9651,
+    'aleph'                    : 8501,
+    'dotminus'                 : 8760,
+    'upsilon'                  : 965,
+    'Lambda'                   : 923,
+    'cap'                      : 8745,
+    'barleftarrow'             : 8676,
+    'mu'                       : 956,
+    'boxplus'                  : 8862,
+    'mp'                       : 8723,
+    'circledast'               : 8859,
+    'tau'                      : 964,
+    'in'                       : 8712,
+    'backslash'                : 92,
+    'varnothing'               : 8709,
+    'sharp'                    : 9839,
+    'eqsim'                    : 8770,
+    'gnsim'                    : 8935,
+    'Searrow'                  : 8664,
+    'updownarrows'             : 8645,
+    'heartsuit'                : 9825,
+    'trianglelefteq'           : 8884,
+    'ddag'                     : 8225,
+    'sqsubseteq'               : 8849,
+    'mapsfrom'                 : 8612,
+    'boxbar'                   : 9707,
+    'sim'                      : 8764,
+    'Nwarrow'                  : 8662,
+    'nequiv'                   : 8802,
+    'succ'                     : 8827,
+    'vdash'                    : 8866,
+    'Leftrightarrow'           : 8660,
+    'parallel'                 : 8741,
+    'invnot'                   : 8976,
+    'natural'                  : 9838,
+    'ss'                       : 223,
+    'uparrow'                  : 8593,
+    'nsim'                     : 8769,
+    'hookrightarrow'           : 8618,
+    'Equiv'                    : 8803,
+    'approx'                   : 8776,
+    'Vvdash'                   : 8874,
+    'nsucc'                    : 8833,
+    'leftrightharpoons'        : 8651,
+    'Re'                       : 8476,
+    'boxminus'                 : 8863,
+    'equiv'                    : 8801,
+    'Lleftarrow'               : 8666,
+    'll'                       : 8810,
+    'Cup'                      : 8915,
+    'measeq'                   : 8798,
+    'upharpoonleft'            : 8639,
+    'lq'                       : 8216,
+    'Upsilon'                  : 933,
+    'subsetneq'                : 8842,
+    'greater'                  : 62,
+    'supsetneq'                : 8843,
+    'Cap'                      : 8914,
+    'L'                        : 321,
+    'spadesuit'                : 9824,
+    'lrcorner'                 : 8991,
+    'not'                      : 824,
+    'bar'                      : 772,
+    'rightharpoonaccent'       : 8401,
+    'boxdot'                   : 8865,
+    'l'                        : 322,
+    'leftharpoondown'          : 8637,
+    'bigcup'                   : 8899,
+    'iint'                     : 8748,
+    'bigwedge'                 : 8896,
+    'downharpoonleft'          : 8643,
+    'textasciitilde'           : 126,
+    'subset'                   : 8834,
+    'leqq'                     : 8806,
+    'mapsup'                   : 8613,
+    'nvDash'                   : 8877,
+    'looparrowleft'            : 8619,
+    'nless'                    : 8814,
+    'rightarrowbar'            : 8677,
+    'Vert'                     : 8214,
+    'downdownarrows'           : 8650,
+    'uplus'                    : 8846,
+    'simeq'                    : 8771,
+    'napprox'                  : 8777,
+    'ast'                      : 8727,
+    'twoheaduparrow'           : 8607,
+    'doublebarwedge'           : 8966,
+    'Sigma'                    : 931,
+    'leftharpoonaccent'        : 8400,
+    'ntrianglelefteq'          : 8940,
+    'nexists'                  : 8708,
+    'times'                    : 215,
+    'measuredangle'            : 8737,
+    'bumpeq'                   : 8783,
+    'carriagereturn'           : 8629,
+    'adots'                    : 8944,
+    'checkmark'                : 10003,
+    'lambda'                   : 955,
+    'xi'                       : 958,
+    'rbrace'                   : 125,
+    'rbrack'                   : 93,
+    'Nearrow'                  : 8663,
+    'maltese'                  : 10016,
+    'clubsuit'                 : 9827,
+    'top'                      : 8868,
+    'overarc'                  : 785,
+    'varphi'                   : 966,
+    'Delta'                    : 916,
+    'iota'                     : 953,
+    'nleftarrow'               : 8602,
+    'candra'                   : 784,
+    'supset'                   : 8835,
+    'triangleleft'             : 9665,
+    'gtreqless'                : 8923,
+    'ntrianglerighteq'         : 8941,
+    'quad'                     : 8195,
+    'Xi'                       : 926,
+    'gtrdot'                   : 8919,
+    'leftthreetimes'           : 8907,
+    'minus'                    : 8722,
+    'preccurlyeq'              : 8828,
+    'nleftrightarrow'          : 8622,
+    'lambdabar'                : 411,
+    'blacktriangle'            : 9652,
+    'kernelcontraction'        : 8763,
+    'Phi'                      : 934,
+    'angle'                    : 8736,
+    'spadesuitopen'            : 9828,
+    'eqless'                   : 8924,
+    'mid'                      : 8739,
+    'varkappa'                 : 1008,
+    'Ldsh'                     : 8626,
+    'updownarrow'              : 8597,
+    'beta'                     : 946,
+    'textquotedblleft'         : 8220,
+    'rho'                      : 961,
+    'alpha'                    : 945,
+    'intercal'                 : 8890,
+    'beth'                     : 8502,
+    'grave'                    : 768,
+    'acwopencirclearrow'       : 8634,
+    'nmid'                     : 8740,
+    'nsupset'                  : 8837,
+    'sigma'                    : 963,
+    'dot'                      : 775,
+    'Rightarrow'               : 8658,
+    'turnednot'                : 8985,
+    'backsimeq'                : 8909,
+    'leftarrowtail'            : 8610,
+    'approxeq'                 : 8778,
+    'curlyeqsucc'              : 8927,
+    'rightarrowtail'           : 8611,
+    'Psi'                      : 936,
+    'copyright'                : 169,
+    'yen'                      : 165,
+    'vartriangleleft'          : 8882,
+    'rasp'                     : 700,
+    'triangleright'            : 9655,
+    'precsim'                  : 8830,
+    'infty'                    : 8734,
+    'geq'                      : 8805,
+    'updownarrowbar'           : 8616,
+    'precnsim'                 : 8936,
+    'H'                        : 779,
+    'ulcorner'                 : 8988,
+    'looparrowright'           : 8620,
+    'ncong'                    : 8775,
+    'downarrow'                : 8595,
+    'circeq'                   : 8791,
+    'subseteq'                 : 8838,
+    'bigstar'                  : 9733,
+    'prime'                    : 8242,
+    'lceil'                    : 8968,
+    'Rrightarrow'              : 8667,
+    'oiiint'                   : 8752,
+    'curlywedge'               : 8911,
+    'vDash'                    : 8872,
+    'lfloor'                   : 8970,
+    'ddots'                    : 8945,
+    'exists'                   : 8707,
+    'underbar'                 : 817,
+    'Pi'                       : 928,
+    'leftrightarrows'          : 8646,
+    'sphericalangle'           : 8738,
+    'coprod'                   : 8720,
+    'circledcirc'              : 8858,
+    'gtrsim'                   : 8819,
+    'gneqq'                    : 8809,
+    'between'                  : 8812,
+    'theta'                    : 952,
+    'complement'               : 8705,
+    'arceq'                    : 8792,
+    'nVdash'                   : 8878,
+    'S'                        : 167,
+    'wr'                       : 8768,
+    'wp'                       : 8472,
+    'backcong'                 : 8780,
+    'lasp'                     : 701,
+    'c'                        : 807,
+    'nabla'                    : 8711,
+    'dotplus'                  : 8724,
+    'eta'                      : 951,
+    'forall'                   : 8704,
+    'eth'                      : 240,
+    'colon'                    : 58,
+    'sqcup'                    : 8852,
+    'rightrightarrows'         : 8649,
+    'sqsupset'                 : 8848,
+    'mapsto'                   : 8614,
+    'bigtriangledown'          : 9661,
+    'sqsupseteq'               : 8850,
+    'propto'                   : 8733,
+    'pi'                       : 960,
+    'pm'                       : 177,
+    'dots'                     : 0x2026,
+    'nrightarrow'              : 8603,
+    'textasciiacute'           : 180,
+    'Doteq'                    : 8785,
+    'breve'                    : 774,
+    'sqcap'                    : 8851,
+    'twoheadrightarrow'        : 8608,
+    'kappa'                    : 954,
+    'vartriangle'              : 9653,
+    'diamondsuit'              : 9826,
+    'pitchfork'                : 8916,
+    'blacktriangleleft'        : 9664,
+    'nprec'                    : 8832,
+    'curvearrowright'          : 8631,
+    'barwedge'                 : 8892,
+    'multimap'                 : 8888,
+    'textquestiondown'         : 191,
+    'cong'                     : 8773,
+    'rtimes'                   : 8906,
+    'rightzigzagarrow'         : 8669,
+    'rightarrow'               : 8594,
+    'leftarrow'                : 8592,
+    '__sqrt__'                 : 8730,
+    'twoheaddownarrow'         : 8609,
+    'oint'                     : 8750,
+    'bigvee'                   : 8897,
+    'eqdef'                    : 8797,
+    'sterling'                 : 163,
+    'phi'                      : 981,
+    'Updownarrow'              : 8661,
+    'backprime'                : 8245,
+    'emdash'                   : 8212,
+    'Gamma'                    : 915,
+    'i'                        : 305,
+    'rceil'                    : 8969,
+    'leftharpoonup'            : 8636,
+    'Im'                       : 8465,
+    'curvearrowleft'           : 8630,
+    'wedgeq'                   : 8793,
+    'curlyeqprec'              : 8926,
+    'questeq'                  : 8799,
+    'less'                     : 60,
+    'upuparrows'               : 8648,
+    'tilde'                    : 771,
+    'textasciigrave'           : 96,
+    'smallsetminus'            : 8726,
+    'ell'                      : 8467,
+    'cup'                      : 8746,
+    'danger'                   : 9761,
+    'nVDash'                   : 8879,
+    'cdotp'                    : 183,
+    'cdots'                    : 8943,
+    'hat'                      : 770,
+    'eqgtr'                    : 8925,
+    'psi'                      : 968,
+    'frown'                    : 8994,
+    'acute'                    : 769,
+    'downzigzagarrow'          : 8623,
+    'ntriangleright'           : 8939,
+    'cupdot'                   : 8845,
+    'circleddash'              : 8861,
+    'oslash'                   : 8856,
+    'mho'                      : 8487,
+    'd'                        : 803,
+    'sqsubset'                 : 8847,
+    'cdot'                     : 8901,
+    'Omega'                    : 937,
+    'OE'                       : 338,
+    'veeeq'                    : 8794,
+    'Finv'                     : 8498,
+    't'                        : 865,
+    'leftrightarrow'           : 8596,
+    'swarrow'                  : 8601,
+    'rightthreetimes'          : 8908,
+    'rightleftharpoons'        : 8652,
+    'lesssim'                  : 8818,
+    'searrow'                  : 8600,
+    'because'                  : 8757,
+    'gtrless'                  : 8823,
+    'star'                     : 8902,
+    'nsubset'                  : 8836,
+    'zeta'                     : 950,
+    'dddot'                    : 8411,
+    'bigcirc'                  : 9675,
+    'Supset'                   : 8913,
+    'circ'                     : 8728,
+    'slash'                    : 8725,
+    'ocirc'                    : 778,
+    'prod'                     : 8719,
+    'twoheadleftarrow'         : 8606,
+    'daleth'                   : 8504,
+    'upharpoonright'           : 8638,
+    'odot'                     : 8857,
+    'Uparrow'                  : 8657,
+    'O'                        : 216,
+    'hookleftarrow'            : 8617,
+    'trianglerighteq'          : 8885,
+    'nsime'                    : 8772,
+    'oe'                       : 339,
+    'nwarrow'                  : 8598,
+    'o'                        : 248,
+    'ddddot'                   : 8412,
+    'downharpoonright'         : 8642,
+    'succcurlyeq'              : 8829,
+    'gamma'                    : 947,
+    'scrR'                     : 8475,
+    'dag'                      : 8224,
+    'thickspace'               : 8197,
+    'frakZ'                    : 8488,
+    'lessdot'                  : 8918,
+    'triangledown'             : 9663,
+    'ltimes'                   : 8905,
+    'scrB'                     : 8492,
+    'endash'                   : 8211,
+    'scrE'                     : 8496,
+    'scrF'                     : 8497,
+    'scrH'                     : 8459,
+    'scrI'                     : 8464,
+    'rightharpoondown'         : 8641,
+    'scrL'                     : 8466,
+    'scrM'                     : 8499,
+    'frakC'                    : 8493,
+    'nsupseteq'                : 8841,
+    'circledR'                 : 174,
+    'circledS'                 : 9416,
+    'ngtr'                     : 8815,
+    'bigcap'                   : 8898,
+    'scre'                     : 8495,
+    'Downarrow'                : 8659,
+    'scrg'                     : 8458,
+    'overleftrightarrow'       : 8417,
+    'scro'                     : 8500,
+    'lnsim'                    : 8934,
+    'eqcolon'                  : 8789,
+    'curlyvee'                 : 8910,
+    'urcorner'                 : 8989,
+    'lbrace'                   : 123,
+    'Bumpeq'                   : 8782,
+    'delta'                    : 948,
+    'boxtimes'                 : 8864,
+    'overleftarrow'            : 8406,
+    'prurel'                   : 8880,
+    'clubsuitopen'             : 9831,
+    'cwopencirclearrow'        : 8635,
+    'geqq'                     : 8807,
+    'rightleftarrows'          : 8644,
+    'ac'                       : 8766,
+    'ae'                       : 230,
+    'int'                      : 8747,
+    'rfloor'                   : 8971,
+    'risingdotseq'             : 8787,
+    'nvdash'                   : 8876,
+    'diamond'                  : 8900,
+    'ddot'                     : 776,
+    'backsim'                  : 8765,
+    'oplus'                    : 8853,
+    'triangleq'                : 8796,
+    'check'                    : 780,
+    'ni'                       : 8715,
+    'iiint'                    : 8749,
+    'ne'                       : 8800,
+    'lesseqgtr'                : 8922,
+    'obar'                     : 9021,
+    'supseteq'                 : 8839,
+    'nu'                       : 957,
+    'AA'                       : 197,
+    'AE'                       : 198,
+    'models'                   : 8871,
+    'ominus'                   : 8854,
+    'dashv'                    : 8867,
+    'omega'                    : 969,
+    'rq'                       : 8217,
+    'Subset'                   : 8912,
+    'rightharpoonup'           : 8640,
+    'Rdsh'                     : 8627,
+    'bullet'                   : 8729,
+    'divideontimes'            : 8903,
+    'lbrack'                   : 91,
+    'textquotedblright'        : 8221,
+    'Colon'                    : 8759,
+    '%'                        : 37,
+    '$'                        : 36,
+    '{'                        : 123,
+    '}'                        : 125,
+    '_'                        : 95,
+    '#'                        : 35,
+    'imath'                    : 0x131,
+    'circumflexaccent'         : 770,
+    'combiningbreve'           : 774,
+    'combiningoverline'        : 772,
+    'combininggraveaccent'     : 768,
+    'combiningacuteaccent'     : 769,
+    'combiningdiaeresis'       : 776,
+    'combiningtilde'           : 771,
+    'combiningrightarrowabove' : 8407,
+    'combiningdotabove'        : 775,
+    'to'                       : 8594,
+    'succeq'                   : 8829,
+    'emptyset'                 : 8709,
+    'leftparen'                : 40,
+    'rightparen'               : 41,
+    'bigoplus'                 : 10753,
+    'leftangle'                : 10216,
+    'rightangle'               : 10217,
+    'leftbrace'                : 124,
+    'rightbrace'               : 125,
+    'jmath'                    : 567,
+    'bigodot'                  : 10752,
+    'preceq'                   : 8828,
+    'biguplus'                 : 10756,
+    'epsilon'                  : 949,
+    'vartheta'                 : 977,
+    'bigotimes'                : 10754,
+    'guillemotleft'            : 171,
+    'ring'                     : 730,
+    'Thorn'                    : 222,
+    'guilsinglright'           : 8250,
+    'perthousand'              : 8240,
+    'macron'                   : 175,
+    'cent'                     : 162,
+    'guillemotright'           : 187,
+    'equal'                    : 61,
+    'asterisk'                 : 42,
+    'guilsinglleft'            : 8249,
+    'plus'                     : 43,
+    'thorn'                    : 254,
+    'dagger'                   : 8224
+}
+
+# Each element is a 4-tuple of the form:
+#   src_start, src_end, dst_font, dst_start
+#
+stix_virtual_fonts = {
+    'bb':
+        {
+        'rm':
+            [
+            (0x0030, 0x0039, 'rm', 0x1d7d8), # 0-9
+            (0x0041, 0x0042, 'rm', 0x1d538), # A-B
+            (0x0043, 0x0043, 'rm', 0x2102),  # C
+            (0x0044, 0x0047, 'rm', 0x1d53b), # D-G
+            (0x0048, 0x0048, 'rm', 0x210d),  # H
+            (0x0049, 0x004d, 'rm', 0x1d540), # I-M
+            (0x004e, 0x004e, 'rm', 0x2115),  # N
+            (0x004f, 0x004f, 'rm', 0x1d546), # O
+            (0x0050, 0x0051, 'rm', 0x2119),  # P-Q
+            (0x0052, 0x0052, 'rm', 0x211d),  # R
+            (0x0053, 0x0059, 'rm', 0x1d54a), # S-Y
+            (0x005a, 0x005a, 'rm', 0x2124),  # Z
+            (0x0061, 0x007a, 'rm', 0x1d552), # a-z
+            (0x0393, 0x0393, 'rm', 0x213e),  # \Gamma
+            (0x03a0, 0x03a0, 'rm', 0x213f),  # \Pi
+            (0x03a3, 0x03a3, 'rm', 0x2140),  # \Sigma
+            (0x03b3, 0x03b3, 'rm', 0x213d),  # \gamma
+            (0x03c0, 0x03c0, 'rm', 0x213c),  # \pi
+            ],
+        'it':
+            [
+            (0x0030, 0x0039, 'rm', 0x1d7d8), # 0-9
+            (0x0041, 0x0042, 'it', 0xe154),  # A-B
+            (0x0043, 0x0043, 'it', 0x2102),  # C
+            (0x0044, 0x0044, 'it', 0x2145),  # D
+            (0x0045, 0x0047, 'it', 0xe156),  # E-G
+            (0x0048, 0x0048, 'it', 0x210d),  # H
+            (0x0049, 0x004d, 'it', 0xe159),  # I-M
+            (0x004e, 0x004e, 'it', 0x2115),  # N
+            (0x004f, 0x004f, 'it', 0xe15e),  # O
+            (0x0050, 0x0051, 'it', 0x2119),  # P-Q
+            (0x0052, 0x0052, 'it', 0x211d),  # R
+            (0x0053, 0x0059, 'it', 0xe15f),  # S-Y
+            (0x005a, 0x005a, 'it', 0x2124),  # Z
+            (0x0061, 0x0063, 'it', 0xe166),  # a-c
+            (0x0064, 0x0065, 'it', 0x2146),  # d-e
+            (0x0066, 0x0068, 'it', 0xe169),  # f-h
+            (0x0069, 0x006a, 'it', 0x2148),  # i-j
+            (0x006b, 0x007a, 'it', 0xe16c),  # k-z
+            (0x0393, 0x0393, 'it', 0x213e),  # \Gamma (not in beta STIX fonts)
+            (0x03a0, 0x03a0, 'it', 0x213f),  # \Pi
+            (0x03a3, 0x03a3, 'it', 0x2140),  # \Sigma (not in beta STIX fonts)
+            (0x03b3, 0x03b3, 'it', 0x213d),  # \gamma (not in beta STIX fonts)
+            (0x03c0, 0x03c0, 'it', 0x213c),  # \pi
+            ],
+        'bf':
+            [
+            (0x0030, 0x0039, 'rm', 0x1d7d8), # 0-9
+            (0x0041, 0x0042, 'bf', 0xe38a),  # A-B
+            (0x0043, 0x0043, 'bf', 0x2102),  # C
+            (0x0044, 0x0044, 'bf', 0x2145),  # D
+            (0x0045, 0x0047, 'bf', 0xe38d),  # E-G
+            (0x0048, 0x0048, 'bf', 0x210d),  # H
+            (0x0049, 0x004d, 'bf', 0xe390),  # I-M
+            (0x004e, 0x004e, 'bf', 0x2115),  # N
+            (0x004f, 0x004f, 'bf', 0xe395),  # O
+            (0x0050, 0x0051, 'bf', 0x2119),  # P-Q
+            (0x0052, 0x0052, 'bf', 0x211d),  # R
+            (0x0053, 0x0059, 'bf', 0xe396),  # S-Y
+            (0x005a, 0x005a, 'bf', 0x2124),  # Z
+            (0x0061, 0x0063, 'bf', 0xe39d),  # a-c
+            (0x0064, 0x0065, 'bf', 0x2146),  # d-e
+            (0x0066, 0x0068, 'bf', 0xe3a2),  # f-h
+            (0x0069, 0x006a, 'bf', 0x2148),  # i-j
+            (0x006b, 0x007a, 'bf', 0xe3a7),  # k-z
+            (0x0393, 0x0393, 'bf', 0x213e),  # \Gamma
+            (0x03a0, 0x03a0, 'bf', 0x213f),  # \Pi
+            (0x03a3, 0x03a3, 'bf', 0x2140),  # \Sigma
+            (0x03b3, 0x03b3, 'bf', 0x213d),  # \gamma
+            (0x03c0, 0x03c0, 'bf', 0x213c),  # \pi
+            ],
+        },
+    'cal':
+        [
+        (0x0041, 0x005a, 'it', 0xe22d), # A-Z
+        ],
+    'circled':
+        {
+        'rm':
+            [
+            (0x0030, 0x0030, 'rm', 0x24ea), # 0
+            (0x0031, 0x0039, 'rm', 0x2460), # 1-9
+            (0x0041, 0x005a, 'rm', 0x24b6), # A-Z
+            (0x0061, 0x007a, 'rm', 0x24d0)  # a-z
+            ],
+        'it':
+            [
+            (0x0030, 0x0030, 'rm', 0x24ea), # 0
+            (0x0031, 0x0039, 'rm', 0x2460), # 1-9
+            (0x0041, 0x005a, 'it', 0x24b6), # A-Z
+            (0x0061, 0x007a, 'it', 0x24d0)  # a-z
+            ],
+        'bf':
+            [
+            (0x0030, 0x0030, 'bf', 0x24ea), # 0
+            (0x0031, 0x0039, 'bf', 0x2460), # 1-9
+            (0x0041, 0x005a, 'bf', 0x24b6), # A-Z
+            (0x0061, 0x007a, 'bf', 0x24d0)  # a-z
+            ],
+        },
+    'frak':
+        {
+        'rm':
+            [
+            (0x0041, 0x0042, 'rm', 0x1d504), # A-B
+            (0x0043, 0x0043, 'rm', 0x212d),  # C
+            (0x0044, 0x0047, 'rm', 0x1d507), # D-G
+            (0x0048, 0x0048, 'rm', 0x210c),  # H
+            (0x0049, 0x0049, 'rm', 0x2111),  # I
+            (0x004a, 0x0051, 'rm', 0x1d50d), # J-Q
+            (0x0052, 0x0052, 'rm', 0x211c),  # R
+            (0x0053, 0x0059, 'rm', 0x1d516), # S-Y
+            (0x005a, 0x005a, 'rm', 0x2128),  # Z
+            (0x0061, 0x007a, 'rm', 0x1d51e), # a-z
+            ],
+        'it':
+            [
+            (0x0041, 0x0042, 'rm', 0x1d504), # A-B
+            (0x0043, 0x0043, 'rm', 0x212d),  # C
+            (0x0044, 0x0047, 'rm', 0x1d507), # D-G
+            (0x0048, 0x0048, 'rm', 0x210c),  # H
+            (0x0049, 0x0049, 'rm', 0x2111),  # I
+            (0x004a, 0x0051, 'rm', 0x1d50d), # J-Q
+            (0x0052, 0x0052, 'rm', 0x211c),  # R
+            (0x0053, 0x0059, 'rm', 0x1d516), # S-Y
+            (0x005a, 0x005a, 'rm', 0x2128),  # Z
+            (0x0061, 0x007a, 'rm', 0x1d51e), # a-z
+            ],
+        'bf':
+            [
+            (0x0041, 0x005a, 'bf', 0x1d56c), # A-Z
+            (0x0061, 0x007a, 'bf', 0x1d586), # a-z
+            ],
+        },
+    'scr':
+        [
+        (0x0041, 0x0041, 'it', 0x1d49c), # A
+        (0x0042, 0x0042, 'it', 0x212c),  # B
+        (0x0043, 0x0044, 'it', 0x1d49e), # C-D
+        (0x0045, 0x0046, 'it', 0x2130),  # E-F
+        (0x0047, 0x0047, 'it', 0x1d4a2), # G
+        (0x0048, 0x0048, 'it', 0x210b),  # H
+        (0x0049, 0x0049, 'it', 0x2110),  # I
+        (0x004a, 0x004b, 'it', 0x1d4a5), # J-K
+        (0x004c, 0x004c, 'it', 0x2112),  # L
+        (0x004d, 0x004d, 'it', 0x2133),  # M
+        (0x004e, 0x0051, 'it', 0x1d4a9), # N-Q
+        (0x0052, 0x0052, 'it', 0x211b),  # R
+        (0x0053, 0x005a, 'it', 0x1d4ae), # S-Z
+        (0x0061, 0x0064, 'it', 0x1d4b6), # a-d
+        (0x0065, 0x0065, 'it', 0x212f),  # e
+        (0x0066, 0x0066, 'it', 0x1d4bb), # f
+        (0x0067, 0x0067, 'it', 0x210a),  # g
+        (0x0068, 0x006e, 'it', 0x1d4bd), # h-n
+        (0x006f, 0x006f, 'it', 0x2134),  # o
+        (0x0070, 0x007a, 'it', 0x1d4c5), # p-z
+        ],
+    'sf':
+        {
+        'rm':
+            [
+            (0x0030, 0x0039, 'rm', 0x1d7e2), # 0-9
+            (0x0041, 0x005a, 'rm', 0x1d5a0), # A-Z
+            (0x0061, 0x007a, 'rm', 0x1d5ba), # a-z
+            (0x0391, 0x03a9, 'rm', 0xe17d),  # \Alpha-\Omega
+            (0x03b1, 0x03c9, 'rm', 0xe196),  # \alpha-\omega
+            (0x03d1, 0x03d1, 'rm', 0xe1b0),  # theta variant
+            (0x03d5, 0x03d5, 'rm', 0xe1b1),  # phi variant
+            (0x03d6, 0x03d6, 'rm', 0xe1b3),  # pi variant
+            (0x03f1, 0x03f1, 'rm', 0xe1b2),  # rho variant
+            (0x03f5, 0x03f5, 'rm', 0xe1af),  # lunate epsilon
+            (0x2202, 0x2202, 'rm', 0xe17c),  # partial differential
+            ],
+        'it':
+            [
+            # These numerals are actually upright.  We don't actually
+            # want italic numerals ever.
+            (0x0030, 0x0039, 'rm', 0x1d7e2), # 0-9
+            (0x0041, 0x005a, 'it', 0x1d608), # A-Z
+            (0x0061, 0x007a, 'it', 0x1d622), # a-z
+            (0x0391, 0x03a9, 'rm', 0xe17d),  # \Alpha-\Omega
+            (0x03b1, 0x03c9, 'it', 0xe1d8),  # \alpha-\omega
+            (0x03d1, 0x03d1, 'it', 0xe1f2),  # theta variant
+            (0x03d5, 0x03d5, 'it', 0xe1f3),  # phi variant
+            (0x03d6, 0x03d6, 'it', 0xe1f5),  # pi variant
+            (0x03f1, 0x03f1, 'it', 0xe1f4),  # rho variant
+            (0x03f5, 0x03f5, 'it', 0xe1f1),  # lunate epsilon
+            ],
+        'bf':
+            [
+            (0x0030, 0x0039, 'bf', 0x1d7ec), # 0-9
+            (0x0041, 0x005a, 'bf', 0x1d5d4), # A-Z
+            (0x0061, 0x007a, 'bf', 0x1d5ee), # a-z
+            (0x0391, 0x03a9, 'bf', 0x1d756), # \Alpha-\Omega
+            (0x03b1, 0x03c9, 'bf', 0x1d770), # \alpha-\omega
+            (0x03d1, 0x03d1, 'bf', 0x1d78b), # theta variant
+            (0x03d5, 0x03d5, 'bf', 0x1d78d), # phi variant
+            (0x03d6, 0x03d6, 'bf', 0x1d78f), # pi variant
+            (0x03f0, 0x03f0, 'bf', 0x1d78c), # kappa variant
+            (0x03f1, 0x03f1, 'bf', 0x1d78e), # rho variant
+            (0x03f5, 0x03f5, 'bf', 0x1d78a), # lunate epsilon
+            (0x2202, 0x2202, 'bf', 0x1d789), # partial differential
+            (0x2207, 0x2207, 'bf', 0x1d76f), # \Nabla
+            ],
+        },
+    'tt':
+        [
+        (0x0030, 0x0039, 'rm', 0x1d7f6), # 0-9
+        (0x0041, 0x005a, 'rm', 0x1d670), # A-Z
+        (0x0061, 0x007a, 'rm', 0x1d68a)  # a-z
+        ],
+    }

BIN
venv/lib/python3.8/site-packages/matplotlib/_path.cpython-38-x86_64-linux-gnu.so


BIN
venv/lib/python3.8/site-packages/matplotlib/_png.cpython-38-x86_64-linux-gnu.so


+ 129 - 0
venv/lib/python3.8/site-packages/matplotlib/_pylab_helpers.py

@@ -0,0 +1,129 @@
+"""
+Manage figures for pyplot interface.
+"""
+
+import atexit
+import gc
+
+
+class Gcf:
+    """
+    Singleton to manage a set of integer-numbered figures.
+
+    This class is never instantiated; it consists of two class
+    attributes (a list and a dictionary), and a set of static
+    methods that operate on those attributes, accessing them
+    directly as class attributes.
+
+    Attributes
+    ----------
+    figs
+          dictionary of the form {*num*: *manager*, ...}
+    _activeQue
+          list of *managers*, with active one at the end
+
+    """
+    _activeQue = []
+    figs = {}
+
+    @classmethod
+    def get_fig_manager(cls, num):
+        """
+        If figure manager *num* exists, make it the active
+        figure and return the manager; otherwise return *None*.
+        """
+        manager = cls.figs.get(num, None)
+        if manager is not None:
+            cls.set_active(manager)
+        return manager
+
+    @classmethod
+    def destroy(cls, num):
+        """
+        Try to remove all traces of figure *num*.
+
+        In the interactive backends, this is bound to the
+        window "destroy" and "delete" events.
+        """
+        if not cls.has_fignum(num):
+            return
+        manager = cls.figs[num]
+        manager.canvas.mpl_disconnect(manager._cidgcf)
+        cls._activeQue.remove(manager)
+        del cls.figs[num]
+        manager.destroy()
+        gc.collect(1)
+
+    @classmethod
+    def destroy_fig(cls, fig):
+        "*fig* is a Figure instance"
+        num = next((manager.num for manager in cls.figs.values()
+                    if manager.canvas.figure == fig), None)
+        if num is not None:
+            cls.destroy(num)
+
+    @classmethod
+    def destroy_all(cls):
+        # this is need to ensure that gc is available in corner cases
+        # where modules are being torn down after install with easy_install
+        import gc  # noqa
+        for manager in list(cls.figs.values()):
+            manager.canvas.mpl_disconnect(manager._cidgcf)
+            manager.destroy()
+
+        cls._activeQue = []
+        cls.figs.clear()
+        gc.collect(1)
+
+    @classmethod
+    def has_fignum(cls, num):
+        """
+        Return *True* if figure *num* exists.
+        """
+        return num in cls.figs
+
+    @classmethod
+    def get_all_fig_managers(cls):
+        """
+        Return a list of figure managers.
+        """
+        return list(cls.figs.values())
+
+    @classmethod
+    def get_num_fig_managers(cls):
+        """
+        Return the number of figures being managed.
+        """
+        return len(cls.figs)
+
+    @classmethod
+    def get_active(cls):
+        """
+        Return the manager of the active figure, or *None*.
+        """
+        if len(cls._activeQue) == 0:
+            return None
+        else:
+            return cls._activeQue[-1]
+
+    @classmethod
+    def set_active(cls, manager):
+        """
+        Make the figure corresponding to *manager* the active one.
+        """
+        oldQue = cls._activeQue[:]
+        cls._activeQue = [m for m in oldQue if m != manager]
+        cls._activeQue.append(manager)
+        cls.figs[manager.num] = manager
+
+    @classmethod
+    def draw_all(cls, force=False):
+        """
+        Redraw all figures registered with the pyplot
+        state machine.
+        """
+        for f_mgr in cls.get_all_fig_managers():
+            if force or f_mgr.canvas.figure.stale:
+                f_mgr.canvas.draw_idle()
+
+atexit.register(Gcf.destroy_all)

BIN
venv/lib/python3.8/site-packages/matplotlib/_qhull.cpython-38-x86_64-linux-gnu.so


+ 38 - 0
venv/lib/python3.8/site-packages/matplotlib/_text_layout.py

@@ -0,0 +1,38 @@
+"""
+Text layouting utilities.
+"""
+
+from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING
+
+
+def layout(string, font, *, kern_mode=KERNING_DEFAULT):
+    """
+    Render *string* with *font*.  For each character in *string*, yield a
+    (glyph-index, x-position) pair.  When such a pair is yielded, the font's
+    glyph is set to the corresponding character.
+
+    Parameters
+    ----------
+    string : str
+        The string to be rendered.
+    font : FT2Font
+        The font.
+    kern_mode : int
+        A FreeType kerning mode.
+
+    Yields
+    ------
+    glyph_index : int
+    x_position : float
+    """
+    x = 0
+    last_glyph_idx = None
+    for char in string:
+        glyph_idx = font.get_char_index(ord(char))
+        kern = (font.get_kerning(last_glyph_idx, glyph_idx, kern_mode)
+                if last_glyph_idx is not None else 0) / 64
+        x += kern
+        glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING)
+        yield glyph_idx, x
+        x += glyph.linearHoriAdvance / 65536
+        last_glyph_idx = glyph_idx

BIN
venv/lib/python3.8/site-packages/matplotlib/_tri.cpython-38-x86_64-linux-gnu.so


+ 21 - 0
venv/lib/python3.8/site-packages/matplotlib/_version.py

@@ -0,0 +1,21 @@
+
+# This file was generated by 'versioneer.py' (0.15) from
+# revision-control system data, or from the parent directory name of an
+# unpacked source archive. Distribution tarballs contain a pre-generated copy
+# of this file.
+
+import json
+import sys
+
+version_json = '''
+{
+ "dirty": false,
+ "error": null,
+ "full-revisionid": "ca3d653536dec38a0c1ac3b80413961ca1bcdda6",
+ "version": "3.2.1"
+}
+'''  # END VERSION_JSON
+
+
+def get_versions():
+    return json.loads(version_json)

+ 530 - 0
venv/lib/python3.8/site-packages/matplotlib/afm.py

@@ -0,0 +1,530 @@
+"""
+This is a python interface to Adobe Font Metrics Files.  Although a
+number of other python implementations exist, and may be more complete
+than this, it was decided not to go with them because they were either:
+
+1) copyrighted or used a non-BSD compatible license
+2) had too many dependencies and a free standing lib was needed
+3) did more than needed and it was easier to write afresh rather than
+   figure out how to get just what was needed.
+
+It is pretty easy to use, and requires only built-in python libs:
+
+>>> import matplotlib as mpl
+>>> from pathlib import Path
+>>> afm_path = Path(mpl.get_data_path(), 'fonts', 'afm', 'ptmr8a.afm')
+>>>
+>>> from matplotlib.afm import AFM
+>>> with afm_path.open('rb') as fh:
+...     afm = AFM(fh)
+>>> afm.string_width_height('What the heck?')
+(6220.0, 694)
+>>> afm.get_fontname()
+'Times-Roman'
+>>> afm.get_kern_dist('A', 'f')
+0
+>>> afm.get_kern_dist('A', 'y')
+-92.0
+>>> afm.get_bbox_char('!')
+[130, -9, 238, 676]
+
+As in the Adobe Font Metrics File Format Specification, all dimensions
+are given in units of 1/1000 of the scale factor (point size) of the font
+being used.
+"""
+
+from collections import namedtuple
+import logging
+import re
+
+
+from ._mathtext_data import uni2type1
+from matplotlib.cbook import deprecated
+
+
+_log = logging.getLogger(__name__)
+
+
+def _to_int(x):
+    # Some AFM files have floats where we are expecting ints -- there is
+    # probably a better way to handle this (support floats, round rather than
+    # truncate).  But I don't know what the best approach is now and this
+    # change to _to_int should at least prevent Matplotlib from crashing on
+    # these.  JDH (2009-11-06)
+    return int(float(x))
+
+
+def _to_float(x):
+    # Some AFM files use "," instead of "." as decimal separator -- this
+    # shouldn't be ambiguous (unless someone is wicked enough to use "," as
+    # thousands separator...).
+    if isinstance(x, bytes):
+        # Encoding doesn't really matter -- if we have codepoints >127 the call
+        # to float() will error anyways.
+        x = x.decode('latin-1')
+    return float(x.replace(',', '.'))
+
+
+def _to_str(x):
+    return x.decode('utf8')
+
+
+def _to_list_of_ints(s):
+    s = s.replace(b',', b' ')
+    return [_to_int(val) for val in s.split()]
+
+
+def _to_list_of_floats(s):
+    return [_to_float(val) for val in s.split()]
+
+
+def _to_bool(s):
+    if s.lower().strip() in (b'false', b'0', b'no'):
+        return False
+    else:
+        return True
+
+
+def _parse_header(fh):
+    """
+    Reads the font metrics header (up to the char metrics) and returns
+    a dictionary mapping *key* to *val*.  *val* will be converted to the
+    appropriate python type as necessary; e.g.:
+
+        * 'False'->False
+        * '0'->0
+        * '-168 -218 1000 898'-> [-168, -218, 1000, 898]
+
+    Dictionary keys are
+
+      StartFontMetrics, FontName, FullName, FamilyName, Weight,
+      ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
+      UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
+      XHeight, Ascender, Descender, StartCharMetrics
+
+    """
+    header_converters = {
+        b'StartFontMetrics': _to_float,
+        b'FontName': _to_str,
+        b'FullName': _to_str,
+        b'FamilyName': _to_str,
+        b'Weight': _to_str,
+        b'ItalicAngle': _to_float,
+        b'IsFixedPitch': _to_bool,
+        b'FontBBox': _to_list_of_ints,
+        b'UnderlinePosition': _to_float,
+        b'UnderlineThickness': _to_float,
+        b'Version': _to_str,
+        # Some AFM files have non-ASCII characters (which are not allowed by
+        # the spec).  Given that there is actually no public API to even access
+        # this field, just return it as straight bytes.
+        b'Notice': lambda x: x,
+        b'EncodingScheme': _to_str,
+        b'CapHeight': _to_float,  # Is the second version a mistake, or
+        b'Capheight': _to_float,  # do some AFM files contain 'Capheight'? -JKS
+        b'XHeight': _to_float,
+        b'Ascender': _to_float,
+        b'Descender': _to_float,
+        b'StdHW': _to_float,
+        b'StdVW': _to_float,
+        b'StartCharMetrics': _to_int,
+        b'CharacterSet': _to_str,
+        b'Characters': _to_int,
+        }
+
+    d = {}
+    first_line = True
+    for line in fh:
+        line = line.rstrip()
+        if line.startswith(b'Comment'):
+            continue
+        lst = line.split(b' ', 1)
+        key = lst[0]
+        if first_line:
+            # AFM spec, Section 4: The StartFontMetrics keyword
+            # [followed by a version number] must be the first line in
+            # the file, and the EndFontMetrics keyword must be the
+            # last non-empty line in the file.  We just check the
+            # first header entry.
+            if key != b'StartFontMetrics':
+                raise RuntimeError('Not an AFM file')
+            first_line = False
+        if len(lst) == 2:
+            val = lst[1]
+        else:
+            val = b''
+        try:
+            converter = header_converters[key]
+        except KeyError:
+            _log.error('Found an unknown keyword in AFM header (was %r)' % key)
+            continue
+        try:
+            d[key] = converter(val)
+        except ValueError:
+            _log.error('Value error parsing header in AFM: %s, %s', key, val)
+            continue
+        if key == b'StartCharMetrics':
+            break
+    else:
+        raise RuntimeError('Bad parse')
+    return d
+
+
+CharMetrics = namedtuple('CharMetrics', 'width, name, bbox')
+CharMetrics.__doc__ = """
+    Represents the character metrics of a single character.
+
+    Notes
+    -----
+    The fields do currently only describe a subset of character metrics
+    information defined in the AFM standard.
+    """
+CharMetrics.width.__doc__ = """The character width (WX)."""
+CharMetrics.name.__doc__ = """The character name (N)."""
+CharMetrics.bbox.__doc__ = """
+    The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*)."""
+
+
+def _parse_char_metrics(fh):
+    """
+    Parse the given filehandle for character metrics information and return
+    the information as dicts.
+
+    It is assumed that the file cursor is on the line behind
+    'StartCharMetrics'.
+
+    Returns
+    -------
+    ascii_d : dict
+         A mapping "ASCII num of the character" to `.CharMetrics`.
+    name_d : dict
+         A mapping "character name" to `.CharMetrics`.
+
+    Notes
+    -----
+    This function is incomplete per the standard, but thus far parses
+    all the sample afm files tried.
+    """
+    required_keys = {'C', 'WX', 'N', 'B'}
+
+    ascii_d = {}
+    name_d = {}
+    for line in fh:
+        # We are defensively letting values be utf8. The spec requires
+        # ascii, but there are non-compliant fonts in circulation
+        line = _to_str(line.rstrip())  # Convert from byte-literal
+        if line.startswith('EndCharMetrics'):
+            return ascii_d, name_d
+        # Split the metric line into a dictionary, keyed by metric identifiers
+        vals = dict(s.strip().split(' ', 1) for s in line.split(';') if s)
+        # There may be other metrics present, but only these are needed
+        if not required_keys.issubset(vals):
+            raise RuntimeError('Bad char metrics line: %s' % line)
+        num = _to_int(vals['C'])
+        wx = _to_float(vals['WX'])
+        name = vals['N']
+        bbox = _to_list_of_floats(vals['B'])
+        bbox = list(map(int, bbox))
+        metrics = CharMetrics(wx, name, bbox)
+        # Workaround: If the character name is 'Euro', give it the
+        # corresponding character code, according to WinAnsiEncoding (see PDF
+        # Reference).
+        if name == 'Euro':
+            num = 128
+        elif name == 'minus':
+            num = ord("\N{MINUS SIGN}")  # 0x2212
+        if num != -1:
+            ascii_d[num] = metrics
+        name_d[name] = metrics
+    raise RuntimeError('Bad parse')
+
+
+def _parse_kern_pairs(fh):
+    """
+    Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
+    values are the kern pair value.  For example, a kern pairs line like
+    ``KPX A y -50``
+
+    will be represented as::
+
+      d[ ('A', 'y') ] = -50
+
+    """
+
+    line = next(fh)
+    if not line.startswith(b'StartKernPairs'):
+        raise RuntimeError('Bad start of kern pairs data: %s' % line)
+
+    d = {}
+    for line in fh:
+        line = line.rstrip()
+        if not line:
+            continue
+        if line.startswith(b'EndKernPairs'):
+            next(fh)  # EndKernData
+            return d
+        vals = line.split()
+        if len(vals) != 4 or vals[0] != b'KPX':
+            raise RuntimeError('Bad kern pairs line: %s' % line)
+        c1, c2, val = _to_str(vals[1]), _to_str(vals[2]), _to_float(vals[3])
+        d[(c1, c2)] = val
+    raise RuntimeError('Bad kern pairs parse')
+
+
+CompositePart = namedtuple('CompositePart', 'name, dx, dy')
+CompositePart.__doc__ = """
+    Represents the information on a composite element of a composite char."""
+CompositePart.name.__doc__ = """Name of the part, e.g. 'acute'."""
+CompositePart.dx.__doc__ = """x-displacement of the part from the origin."""
+CompositePart.dy.__doc__ = """y-displacement of the part from the origin."""
+
+
+def _parse_composites(fh):
+    """
+    Parse the given filehandle for composites information return them as a
+    dict.
+
+    It is assumed that the file cursor is on the line behind 'StartComposites'.
+
+    Returns
+    -------
+    composites : dict
+        A dict mapping composite character names to a parts list. The parts
+        list is a list of `.CompositePart` entries describing the parts of
+        the composite.
+
+    Example
+    -------
+    A composite definition line::
+
+      CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
+
+    will be represented as::
+
+      composites['Aacute'] = [CompositePart(name='A', dx=0, dy=0),
+                              CompositePart(name='acute', dx=160, dy=170)]
+
+    """
+    composites = {}
+    for line in fh:
+        line = line.rstrip()
+        if not line:
+            continue
+        if line.startswith(b'EndComposites'):
+            return composites
+        vals = line.split(b';')
+        cc = vals[0].split()
+        name, numParts = cc[1], _to_int(cc[2])
+        pccParts = []
+        for s in vals[1:-1]:
+            pcc = s.split()
+            part = CompositePart(pcc[1], _to_float(pcc[2]), _to_float(pcc[3]))
+            pccParts.append(part)
+        composites[name] = pccParts
+
+    raise RuntimeError('Bad composites parse')
+
+
+def _parse_optional(fh):
+    """
+    Parse the optional fields for kern pair data and composites.
+
+    Returns
+    -------
+    kern_data : dict
+        A dict containing kerning information. May be empty.
+        See `._parse_kern_pairs`.
+    composites : dict
+        A dict containing composite information. May be empty.
+        See `._parse_composites`.
+    """
+    optional = {
+        b'StartKernData': _parse_kern_pairs,
+        b'StartComposites':  _parse_composites,
+        }
+
+    d = {b'StartKernData': {},
+         b'StartComposites': {}}
+    for line in fh:
+        line = line.rstrip()
+        if not line:
+            continue
+        key = line.split()[0]
+
+        if key in optional:
+            d[key] = optional[key](fh)
+
+    return d[b'StartKernData'], d[b'StartComposites']
+
+
+class AFM:
+
+    def __init__(self, fh):
+        """Parse the AFM file in file object *fh*."""
+        self._header = _parse_header(fh)
+        self._metrics, self._metrics_by_name = _parse_char_metrics(fh)
+        self._kern, self._composite = _parse_optional(fh)
+
+    def get_bbox_char(self, c, isord=False):
+        if not isord:
+            c = ord(c)
+        return self._metrics[c].bbox
+
+    def string_width_height(self, s):
+        """
+        Return the string width (including kerning) and string height
+        as a (*w*, *h*) tuple.
+        """
+        if not len(s):
+            return 0, 0
+        total_width = 0
+        namelast = None
+        miny = 1e9
+        maxy = 0
+        for c in s:
+            if c == '\n':
+                continue
+            wx, name, bbox = self._metrics[ord(c)]
+
+            total_width += wx + self._kern.get((namelast, name), 0)
+            l, b, w, h = bbox
+            miny = min(miny, b)
+            maxy = max(maxy, b + h)
+
+            namelast = name
+
+        return total_width, maxy - miny
+
+    def get_str_bbox_and_descent(self, s):
+        """Return the string bounding box and the maximal descent."""
+        if not len(s):
+            return 0, 0, 0, 0, 0
+        total_width = 0
+        namelast = None
+        miny = 1e9
+        maxy = 0
+        left = 0
+        if not isinstance(s, str):
+            s = _to_str(s)
+        for c in s:
+            if c == '\n':
+                continue
+            name = uni2type1.get(ord(c), 'question')
+            try:
+                wx, _, bbox = self._metrics_by_name[name]
+            except KeyError:
+                name = 'question'
+                wx, _, bbox = self._metrics_by_name[name]
+            total_width += wx + self._kern.get((namelast, name), 0)
+            l, b, w, h = bbox
+            left = min(left, l)
+            miny = min(miny, b)
+            maxy = max(maxy, b + h)
+
+            namelast = name
+
+        return left, miny, total_width, maxy - miny, -miny
+
+    def get_str_bbox(self, s):
+        """Return the string bounding box."""
+        return self.get_str_bbox_and_descent(s)[:4]
+
+    def get_name_char(self, c, isord=False):
+        """Get the name of the character, i.e., ';' is 'semicolon'."""
+        if not isord:
+            c = ord(c)
+        return self._metrics[c].name
+
+    def get_width_char(self, c, isord=False):
+        """
+        Get the width of the character from the character metric WX field.
+        """
+        if not isord:
+            c = ord(c)
+        return self._metrics[c].width
+
+    def get_width_from_char_name(self, name):
+        """Get the width of the character from a type1 character name."""
+        return self._metrics_by_name[name].width
+
+    def get_height_char(self, c, isord=False):
+        """Get the bounding box (ink) height of character *c* (space is 0)."""
+        if not isord:
+            c = ord(c)
+        return self._metrics[c].bbox[-1]
+
+    def get_kern_dist(self, c1, c2):
+        """
+        Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
+        """
+        name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
+        return self.get_kern_dist_from_name(name1, name2)
+
+    def get_kern_dist_from_name(self, name1, name2):
+        """
+        Return the kerning pair distance (possibly 0) for chars
+        *name1* and *name2*.
+        """
+        return self._kern.get((name1, name2), 0)
+
+    def get_fontname(self):
+        """Return the font name, e.g., 'Times-Roman'."""
+        return self._header[b'FontName']
+
+    def get_fullname(self):
+        """Return the font full name, e.g., 'Times-Roman'."""
+        name = self._header.get(b'FullName')
+        if name is None:  # use FontName as a substitute
+            name = self._header[b'FontName']
+        return name
+
+    def get_familyname(self):
+        """Return the font family name, e.g., 'Times'."""
+        name = self._header.get(b'FamilyName')
+        if name is not None:
+            return name
+
+        # FamilyName not specified so we'll make a guess
+        name = self.get_fullname()
+        extras = (r'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
+                  r'light|ultralight|extra|condensed))+$')
+        return re.sub(extras, '', name)
+
+    @property
+    def family_name(self):
+        """The font family name, e.g., 'Times'."""
+        return self.get_familyname()
+
+    def get_weight(self):
+        """Return the font weight, e.g., 'Bold' or 'Roman'."""
+        return self._header[b'Weight']
+
+    def get_angle(self):
+        """Return the fontangle as float."""
+        return self._header[b'ItalicAngle']
+
+    def get_capheight(self):
+        """Return the cap height as float."""
+        return self._header[b'CapHeight']
+
+    def get_xheight(self):
+        """Return the xheight as float."""
+        return self._header[b'XHeight']
+
+    def get_underline_thickness(self):
+        """Return the underline thickness as float."""
+        return self._header[b'UnderlineThickness']
+
+    def get_horizontal_stem_width(self):
+        """
+        Return the standard horizontal stem width as float, or *None* if
+        not specified in AFM file.
+        """
+        return self._header.get(b'StdHW', None)
+
+    def get_vertical_stem_width(self):
+        """
+        Return the standard vertical stem width as float, or *None* if
+        not specified in AFM file.
+        """
+        return self._header.get(b'StdVW', None)

+ 1775 - 0
venv/lib/python3.8/site-packages/matplotlib/animation.py

@@ -0,0 +1,1775 @@
+# TODO:
+# * Documentation -- this will need a new section of the User's Guide.
+#      Both for Animations and just timers.
+#   - Also need to update http://www.scipy.org/Cookbook/Matplotlib/Animations
+# * Blit
+#   * Currently broken with Qt4 for widgets that don't start on screen
+#   * Still a few edge cases that aren't working correctly
+#   * Can this integrate better with existing matplotlib animation artist flag?
+#     - If animated removes from default draw(), perhaps we could use this to
+#       simplify initial draw.
+# * Example
+#   * Frameless animation - pure procedural with no loop
+#   * Need example that uses something like inotify or subprocess
+#   * Complex syncing examples
+# * Movies
+#   * Can blit be enabled for movies?
+# * Need to consider event sources to allow clicking through multiple figures
+
+import abc
+import base64
+import contextlib
+from io import BytesIO, TextIOWrapper
+import itertools
+import logging
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+from tempfile import TemporaryDirectory
+import uuid
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib._animation_data import (
+    DISPLAY_TEMPLATE, INCLUDED_FRAMES, JS_INCLUDE, STYLE_INCLUDE)
+from matplotlib import cbook, rcParams, rc_context
+
+
+_log = logging.getLogger(__name__)
+
+# Process creation flag for subprocess to prevent it raising a terminal
+# window. See for example:
+# https://stackoverflow.com/questions/24130623/using-python-subprocess-popen-cant-prevent-exe-stopped-working-prompt
+if sys.platform == 'win32':
+    subprocess_creation_flags = CREATE_NO_WINDOW = 0x08000000
+else:
+    # Apparently None won't work here
+    subprocess_creation_flags = 0
+
+# Other potential writing methods:
+# * http://pymedia.org/
+# * libming (produces swf) python wrappers: https://github.com/libming/libming
+# * Wrap x264 API:
+
+# (http://stackoverflow.com/questions/2940671/
+# how-to-encode-series-of-images-into-h264-using-x264-api-c-c )
+
+
+def adjusted_figsize(w, h, dpi, n):
+    '''Compute figure size so that pixels are a multiple of n
+
+    Parameters
+    ----------
+    w, h : float
+        Size in inches
+
+    dpi : float
+        The dpi
+
+    n : int
+        The target multiple
+
+    Returns
+    -------
+    wnew, hnew : float
+        The new figure size in inches.
+    '''
+
+    # this maybe simplified if / when we adopt consistent rounding for
+    # pixel size across the whole library
+    def correct_roundoff(x, dpi, n):
+        if int(x*dpi) % n != 0:
+            if int(np.nextafter(x, np.inf)*dpi) % n == 0:
+                x = np.nextafter(x, np.inf)
+            elif int(np.nextafter(x, -np.inf)*dpi) % n == 0:
+                x = np.nextafter(x, -np.inf)
+        return x
+
+    wnew = int(w * dpi / n) * n / dpi
+    hnew = int(h * dpi / n) * n / dpi
+    return (correct_roundoff(wnew, dpi, n), correct_roundoff(hnew, dpi, n))
+
+
+# A registry for available MovieWriter classes
+class MovieWriterRegistry:
+    '''Registry of available writer classes by human readable name.'''
+    def __init__(self):
+        self._registered = dict()
+
+    @cbook.deprecated("3.2")
+    def set_dirty(self):
+        """Sets a flag to re-setup the writers."""
+
+    def register(self, name):
+        """Decorator for registering a class under a name.
+
+        Example use::
+
+            @registry.register(name)
+            class Foo:
+                pass
+        """
+        def wrapper(writer_cls):
+            self._registered[name] = writer_cls
+            return writer_cls
+        return wrapper
+
+    @cbook.deprecated("3.2")
+    def ensure_not_dirty(self):
+        """If dirty, reasks the writers if they are available"""
+
+    @cbook.deprecated("3.2")
+    def reset_available_writers(self):
+        """Reset the available state of all registered writers"""
+
+    @cbook.deprecated("3.2")
+    @property
+    def avail(self):
+        return {name: self._registered[name] for name in self.list()}
+
+    def is_available(self, name):
+        """
+        Check if given writer is available by name.
+
+        Parameters
+        ----------
+        name : str
+
+        Returns
+        -------
+        available : bool
+        """
+        try:
+            cls = self._registered[name]
+        except KeyError:
+            return False
+        return cls.isAvailable()
+
+    def __iter__(self):
+        """Iterate over names of available writer class."""
+        for name in self._registered:
+            if self.is_available(name):
+                yield name
+
+    def list(self):
+        """Get a list of available MovieWriters."""
+        return [*self]
+
+    def __getitem__(self, name):
+        """Get an available writer class from its name."""
+        if self.is_available(name):
+            return self._registered[name]
+        raise RuntimeError(f"Requested MovieWriter ({name}) not available")
+
+
+writers = MovieWriterRegistry()
+
+
+class AbstractMovieWriter(abc.ABC):
+    '''
+    Abstract base class for writing movies. Fundamentally, what a MovieWriter
+    does is provide is a way to grab frames by calling grab_frame().
+
+    setup() is called to start the process and finish() is called afterwards.
+
+    This class is set up to provide for writing movie frame data to a pipe.
+    saving() is provided as a context manager to facilitate this process as::
+
+        with moviewriter.saving(fig, outfile='myfile.mp4', dpi=100):
+            # Iterate over frames
+            moviewriter.grab_frame(**savefig_kwargs)
+
+    The use of the context manager ensures that setup() and finish() are
+    performed as necessary.
+
+    An instance of a concrete subclass of this class can be given as the
+    ``writer`` argument of `Animation.save()`.
+    '''
+
+    @abc.abstractmethod
+    def setup(self, fig, outfile, dpi=None):
+        '''
+        Perform setup for writing the movie file.
+
+        Parameters
+        ----------
+        fig : `~matplotlib.figure.Figure`
+            The figure object that contains the information for frames
+        outfile : str
+            The filename of the resulting movie file
+        dpi : int, optional
+            The DPI (or resolution) for the file.  This controls the size
+            in pixels of the resulting movie file. Default is ``fig.dpi``.
+        '''
+
+    @abc.abstractmethod
+    def grab_frame(self, **savefig_kwargs):
+        '''
+        Grab the image information from the figure and save as a movie frame.
+
+        All keyword arguments in savefig_kwargs are passed on to the `savefig`
+        command that saves the figure.
+        '''
+
+    @abc.abstractmethod
+    def finish(self):
+        '''Finish any processing for writing the movie.'''
+
+    @contextlib.contextmanager
+    def saving(self, fig, outfile, dpi, *args, **kwargs):
+        '''
+        Context manager to facilitate writing the movie file.
+
+        ``*args, **kw`` are any parameters that should be passed to `setup`.
+        '''
+        # This particular sequence is what contextlib.contextmanager wants
+        self.setup(fig, outfile, dpi, *args, **kwargs)
+        try:
+            yield self
+        finally:
+            self.finish()
+
+
+class MovieWriter(AbstractMovieWriter):
+    """
+    Base class for writing movies.
+
+    This is a base class for MovieWriter subclasses that write a movie frame
+    data to a pipe. You cannot instantiate this class directly.
+    See examples for how to use its subclasses.
+
+    Attributes
+    ----------
+    frame_format : str
+        The format used in writing frame data, defaults to 'rgba'.
+    fig : `~matplotlib.figure.Figure`
+        The figure to capture data from.
+        This must be provided by the sub-classes.
+
+    """
+
+    def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
+                 metadata=None):
+        """
+        Parameters
+        ----------
+        fps : int
+            Framerate for movie.
+        codec : str or None, optional
+            The codec to use. If ``None`` (the default) :rc:`animation.codec`
+            is used.
+        bitrate : int or None, optional
+            The bitrate for the saved movie file, which is one way to control
+            the output file size and quality. The default value is ``None``,
+            which uses :rc:`animation.bitrate`.  A value of -1 implies that
+            the bitrate should be determined automatically by the underlying
+            utility.
+        extra_args : list of str or None, optional
+            A list of extra string arguments to be passed to the underlying
+            movie utility. The default is ``None``, which passes the additional
+            arguments in :rc:`animation.extra_args`.
+        metadata : Dict[str, str] or None
+            A dictionary of keys and values for metadata to include in the
+            output file. Some keys that may be of use include:
+            title, artist, genre, subject, copyright, srcform, comment.
+        """
+        if self.__class__ is MovieWriter:
+            # TODO MovieWriter is still an abstract class and needs to be
+            #      extended with a mixin. This should be clearer in naming
+            #      and description. For now, just give a reasonable error
+            #      message to users.
+            raise TypeError(
+                'MovieWriter cannot be instantiated directly. Please use one '
+                'of its subclasses.')
+
+        self.fps = fps
+        self.frame_format = 'rgba'
+
+        if codec is None:
+            self.codec = rcParams['animation.codec']
+        else:
+            self.codec = codec
+
+        if bitrate is None:
+            self.bitrate = rcParams['animation.bitrate']
+        else:
+            self.bitrate = bitrate
+
+        if extra_args is None:
+            self.extra_args = list(rcParams[self.args_key])
+        else:
+            self.extra_args = extra_args
+
+        if metadata is None:
+            self.metadata = dict()
+        else:
+            self.metadata = metadata
+
+    @property
+    def frame_size(self):
+        '''A tuple ``(width, height)`` in pixels of a movie frame.'''
+        w, h = self.fig.get_size_inches()
+        return int(w * self.dpi), int(h * self.dpi)
+
+    def _adjust_frame_size(self):
+        if self.codec == 'h264':
+            wo, ho = self.fig.get_size_inches()
+            w, h = adjusted_figsize(wo, ho, self.dpi, 2)
+            if (wo, ho) != (w, h):
+                self.fig.set_size_inches(w, h, forward=True)
+                _log.info('figure size in inches has been adjusted '
+                          'from %s x %s to %s x %s', wo, ho, w, h)
+        else:
+            w, h = self.fig.get_size_inches()
+        _log.debug('frame size in pixels is %s x %s', *self.frame_size)
+        return w, h
+
+    def setup(self, fig, outfile, dpi=None):
+        '''
+        Perform setup for writing the movie file.
+
+        Parameters
+        ----------
+        fig : `~matplotlib.figure.Figure`
+            The figure object that contains the information for frames
+        outfile : str
+            The filename of the resulting movie file
+        dpi : int, optional
+            The DPI (or resolution) for the file.  This controls the size
+            in pixels of the resulting movie file. Default is fig.dpi.
+        '''
+        self.outfile = outfile
+        self.fig = fig
+        if dpi is None:
+            dpi = self.fig.dpi
+        self.dpi = dpi
+        self._w, self._h = self._adjust_frame_size()
+
+        # Run here so that grab_frame() can write the data to a pipe. This
+        # eliminates the need for temp files.
+        self._run()
+
+    def _run(self):
+        # Uses subprocess to call the program for assembling frames into a
+        # movie file.  *args* returns the sequence of command line arguments
+        # from a few configuration options.
+        command = self._args()
+        _log.info('MovieWriter._run: running command: %s',
+                  cbook._pformat_subprocess(command))
+        PIPE = subprocess.PIPE
+        self._proc = subprocess.Popen(
+            command, stdin=PIPE, stdout=PIPE, stderr=PIPE,
+            creationflags=subprocess_creation_flags)
+
+    def finish(self):
+        '''Finish any processing for writing the movie.'''
+        self.cleanup()
+
+    def grab_frame(self, **savefig_kwargs):
+        '''
+        Grab the image information from the figure and save as a movie frame.
+
+        All keyword arguments in savefig_kwargs are passed on to the `savefig`
+        command that saves the figure.
+        '''
+        _log.debug('MovieWriter.grab_frame: Grabbing frame.')
+        # re-adjust the figure size in case it has been changed by the
+        # user.  We must ensure that every frame is the same size or
+        # the movie will not save correctly.
+        self.fig.set_size_inches(self._w, self._h)
+        # Tell the figure to save its data to the sink, using the
+        # frame format and dpi.
+        self.fig.savefig(self._frame_sink(), format=self.frame_format,
+                         dpi=self.dpi, **savefig_kwargs)
+
+    def _frame_sink(self):
+        '''Return the place to which frames should be written.'''
+        return self._proc.stdin
+
+    def _args(self):
+        '''Assemble list of utility-specific command-line arguments.'''
+        return NotImplementedError("args needs to be implemented by subclass.")
+
+    def cleanup(self):
+        '''Clean-up and collect the process used to write the movie file.'''
+        out, err = self._proc.communicate()
+        self._frame_sink().close()
+        # Use the encoding/errors that universal_newlines would use.
+        out = TextIOWrapper(BytesIO(out)).read()
+        err = TextIOWrapper(BytesIO(err)).read()
+        if out:
+            _log.log(
+                logging.WARNING if self._proc.returncode else logging.DEBUG,
+                "MovieWriter stdout:\n%s", out)
+        if err:
+            _log.log(
+                logging.WARNING if self._proc.returncode else logging.DEBUG,
+                "MovieWriter stderr:\n%s", err)
+        if self._proc.returncode:
+            raise subprocess.CalledProcessError(
+                self._proc.returncode, self._proc.args, out, err)
+
+    @classmethod
+    def bin_path(cls):
+        '''
+        Return the binary path to the commandline tool used by a specific
+        subclass. This is a class method so that the tool can be looked for
+        before making a particular MovieWriter subclass available.
+        '''
+        return str(rcParams[cls.exec_key])
+
+    @classmethod
+    def isAvailable(cls):
+        '''
+        Check to see if a MovieWriter subclass is actually available.
+        '''
+        return shutil.which(cls.bin_path()) is not None
+
+
+class FileMovieWriter(MovieWriter):
+    '''`MovieWriter` for writing to individual files and stitching at the end.
+
+    This must be sub-classed to be useful.
+    '''
+    def __init__(self, *args, **kwargs):
+        MovieWriter.__init__(self, *args, **kwargs)
+        self.frame_format = rcParams['animation.frame_format']
+
+    def setup(self, fig, outfile, dpi=None, frame_prefix='_tmp',
+              clear_temp=True):
+        '''Perform setup for writing the movie file.
+
+        Parameters
+        ----------
+        fig : `~matplotlib.figure.Figure`
+            The figure to grab the rendered frames from.
+        outfile : str
+            The filename of the resulting movie file.
+        dpi : number, optional
+            The dpi of the output file. This, with the figure size,
+            controls the size in pixels of the resulting movie file.
+            Default is fig.dpi.
+        frame_prefix : str, optional
+            The filename prefix to use for temporary files.  Defaults to
+            ``'_tmp'``.
+        clear_temp : bool, optional
+            If the temporary files should be deleted after stitching
+            the final result.  Setting this to ``False`` can be useful for
+            debugging.  Defaults to ``True``.
+
+        '''
+        self.fig = fig
+        self.outfile = outfile
+        if dpi is None:
+            dpi = self.fig.dpi
+        self.dpi = dpi
+        self._adjust_frame_size()
+
+        self.clear_temp = clear_temp
+        self.temp_prefix = frame_prefix
+        self._frame_counter = 0  # used for generating sequential file names
+        self._temp_paths = list()
+        self.fname_format_str = '%s%%07d.%s'
+
+    @property
+    def frame_format(self):
+        '''
+        Format (png, jpeg, etc.) to use for saving the frames, which can be
+        decided by the individual subclasses.
+        '''
+        return self._frame_format
+
+    @frame_format.setter
+    def frame_format(self, frame_format):
+        if frame_format in self.supported_formats:
+            self._frame_format = frame_format
+        else:
+            self._frame_format = self.supported_formats[0]
+
+    def _base_temp_name(self):
+        # Generates a template name (without number) given the frame format
+        # for extension and the prefix.
+        return self.fname_format_str % (self.temp_prefix, self.frame_format)
+
+    def _frame_sink(self):
+        # Creates a filename for saving using the basename and the current
+        # counter.
+        path = Path(self._base_temp_name() % self._frame_counter)
+
+        # Save the filename so we can delete it later if necessary
+        self._temp_paths.append(path)
+        _log.debug('FileMovieWriter.frame_sink: saving frame %d to path=%s',
+                   self._frame_counter, path)
+        self._frame_counter += 1  # Ensures each created name is 'unique'
+
+        # This file returned here will be closed once it's used by savefig()
+        # because it will no longer be referenced and will be gc-ed.
+        return open(path, 'wb')
+
+    def grab_frame(self, **savefig_kwargs):
+        '''
+        Grab the image information from the figure and save as a movie frame.
+        All keyword arguments in savefig_kwargs are passed on to the `savefig`
+        command that saves the figure.
+        '''
+        # Overloaded to explicitly close temp file.
+        _log.debug('MovieWriter.grab_frame: Grabbing frame.')
+        # Tell the figure to save its data to the sink, using the
+        # frame format and dpi.
+        with self._frame_sink() as myframesink:
+            self.fig.savefig(myframesink, format=self.frame_format,
+                             dpi=self.dpi, **savefig_kwargs)
+
+    def finish(self):
+        # Call run here now that all frame grabbing is done. All temp files
+        # are available to be assembled.
+        self._run()
+        MovieWriter.finish(self)  # Will call clean-up
+
+    def cleanup(self):
+        MovieWriter.cleanup(self)
+
+        # Delete temporary files
+        if self.clear_temp:
+            _log.debug('MovieWriter: clearing temporary paths=%s',
+                       self._temp_paths)
+            for path in self._temp_paths:
+                path.unlink()
+
+
+@writers.register('pillow')
+class PillowWriter(MovieWriter):
+    @classmethod
+    def isAvailable(cls):
+        try:
+            import PIL
+        except ImportError:
+            return False
+        return True
+
+    def __init__(self, *args, **kwargs):
+        if kwargs.get("extra_args") is None:
+            kwargs["extra_args"] = ()
+        super().__init__(*args, **kwargs)
+
+    def setup(self, fig, outfile, dpi=None):
+        self._frames = []
+        self._outfile = outfile
+        self._dpi = dpi
+        self._fig = fig
+
+    def grab_frame(self, **savefig_kwargs):
+        from PIL import Image
+        buf = BytesIO()
+        self._fig.savefig(buf, **dict(savefig_kwargs, format="rgba"))
+        renderer = self._fig.canvas.get_renderer()
+        self._frames.append(Image.frombuffer(
+            "RGBA",
+            (int(renderer.width), int(renderer.height)), buf.getbuffer(),
+            "raw", "RGBA", 0, 1))
+
+    def finish(self):
+        self._frames[0].save(
+            self._outfile, save_all=True, append_images=self._frames[1:],
+            duration=int(1000 / self.fps), loop=0)
+
+
+# Base class of ffmpeg information. Has the config keys and the common set
+# of arguments that controls the *output* side of things.
+class FFMpegBase:
+    '''Mixin class for FFMpeg output.
+
+    To be useful this must be multiply-inherited from with a
+    `MovieWriterBase` sub-class.
+    '''
+
+    exec_key = 'animation.ffmpeg_path'
+    args_key = 'animation.ffmpeg_args'
+
+    @property
+    def output_args(self):
+        args = ['-vcodec', self.codec]
+        # For h264, the default format is yuv444p, which is not compatible
+        # with quicktime (and others). Specifying yuv420p fixes playback on
+        # iOS, as well as HTML5 video in firefox and safari (on both Win and
+        # OSX). Also fixes internet explorer. This is as of 2015/10/29.
+        if self.codec == 'h264' and '-pix_fmt' not in self.extra_args:
+            args.extend(['-pix_fmt', 'yuv420p'])
+        # The %dk adds 'k' as a suffix so that ffmpeg treats our bitrate as in
+        # kbps
+        if self.bitrate > 0:
+            args.extend(['-b', '%dk' % self.bitrate])
+        if self.extra_args:
+            args.extend(self.extra_args)
+        for k, v in self.metadata.items():
+            args.extend(['-metadata', '%s=%s' % (k, v)])
+
+        return args + ['-y', self.outfile]
+
+    @classmethod
+    def isAvailable(cls):
+        return (
+            super().isAvailable()
+            # Ubuntu 12.04 ships a broken ffmpeg binary which we shouldn't use.
+            # NOTE: when removed, remove the same method in AVConvBase.
+            and b'LibAv' not in subprocess.run(
+                [cls.bin_path()], creationflags=subprocess_creation_flags,
+                stdout=subprocess.DEVNULL, stderr=subprocess.PIPE).stderr)
+
+
+# Combine FFMpeg options with pipe-based writing
+@writers.register('ffmpeg')
+class FFMpegWriter(FFMpegBase, MovieWriter):
+    '''Pipe-based ffmpeg writer.
+
+    Frames are streamed directly to ffmpeg via a pipe and written in a single
+    pass.
+    '''
+    def _args(self):
+        # Returns the command line parameters for subprocess to use
+        # ffmpeg to create a movie using a pipe.
+        args = [self.bin_path(), '-f', 'rawvideo', '-vcodec', 'rawvideo',
+                '-s', '%dx%d' % self.frame_size, '-pix_fmt', self.frame_format,
+                '-r', str(self.fps)]
+        # Logging is quieted because subprocess.PIPE has limited buffer size.
+        # If you have a lot of frames in your animation and set logging to
+        # DEBUG, you will have a buffer overrun.
+        if _log.getEffectiveLevel() > logging.DEBUG:
+            args += ['-loglevel', 'error']
+        args += ['-i', 'pipe:'] + self.output_args
+        return args
+
+
+# Combine FFMpeg options with temp file-based writing
+@writers.register('ffmpeg_file')
+class FFMpegFileWriter(FFMpegBase, FileMovieWriter):
+    '''File-based ffmpeg writer.
+
+    Frames are written to temporary files on disk and then stitched
+    together at the end.
+
+    '''
+    supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp',
+                         'pbm', 'raw', 'rgba']
+
+    def _args(self):
+        # Returns the command line parameters for subprocess to use
+        # ffmpeg to create a movie using a collection of temp images
+        return [self.bin_path(), '-r', str(self.fps),
+                '-i', self._base_temp_name(),
+                '-vframes', str(self._frame_counter)] + self.output_args
+
+
+# Base class of avconv information.  AVConv has identical arguments to FFMpeg.
+class AVConvBase(FFMpegBase):
+    '''Mixin class for avconv output.
+
+    To be useful this must be multiply-inherited from with a
+    `MovieWriterBase` sub-class.
+    '''
+
+    exec_key = 'animation.avconv_path'
+    args_key = 'animation.avconv_args'
+
+    # NOTE : should be removed when the same method is removed in FFMpegBase.
+    isAvailable = classmethod(MovieWriter.isAvailable.__func__)
+
+
+# Combine AVConv options with pipe-based writing
+@writers.register('avconv')
+class AVConvWriter(AVConvBase, FFMpegWriter):
+    '''Pipe-based avconv writer.
+
+    Frames are streamed directly to avconv via a pipe and written in a single
+    pass.
+    '''
+
+
+# Combine AVConv options with file-based writing
+@writers.register('avconv_file')
+class AVConvFileWriter(AVConvBase, FFMpegFileWriter):
+    '''File-based avconv writer.
+
+    Frames are written to temporary files on disk and then stitched
+    together at the end.
+    '''
+
+
+# Base class for animated GIFs with ImageMagick
+class ImageMagickBase:
+    '''Mixin class for ImageMagick output.
+
+    To be useful this must be multiply-inherited from with a
+    `MovieWriterBase` sub-class.
+    '''
+
+    exec_key = 'animation.convert_path'
+    args_key = 'animation.convert_args'
+
+    @property
+    def delay(self):
+        return 100. / self.fps
+
+    @property
+    def output_args(self):
+        return [self.outfile]
+
+    @classmethod
+    def bin_path(cls):
+        binpath = super().bin_path()
+        if binpath == 'convert':
+            binpath = mpl._get_executable_info('magick').executable
+        return binpath
+
+    @classmethod
+    def isAvailable(cls):
+        try:
+            return super().isAvailable()
+        except mpl.ExecutableNotFoundError as _enf:
+            # May be raised by get_executable_info.
+            _log.debug('ImageMagick unavailable due to: %s', _enf)
+            return False
+
+
+# Combine ImageMagick options with pipe-based writing
+@writers.register('imagemagick')
+class ImageMagickWriter(ImageMagickBase, MovieWriter):
+    '''Pipe-based animated gif.
+
+    Frames are streamed directly to ImageMagick via a pipe and written
+    in a single pass.
+
+    '''
+    def _args(self):
+        return ([self.bin_path(),
+                 '-size', '%ix%i' % self.frame_size, '-depth', '8',
+                 '-delay', str(self.delay), '-loop', '0',
+                 '%s:-' % self.frame_format]
+                + self.output_args)
+
+
+# Combine ImageMagick options with temp file-based writing
+@writers.register('imagemagick_file')
+class ImageMagickFileWriter(ImageMagickBase, FileMovieWriter):
+    '''File-based animated gif writer.
+
+    Frames are written to temporary files on disk and then stitched
+    together at the end.
+
+    '''
+
+    supported_formats = ['png', 'jpeg', 'ppm', 'tiff', 'sgi', 'bmp',
+                         'pbm', 'raw', 'rgba']
+
+    def _args(self):
+        return ([self.bin_path(), '-delay', str(self.delay), '-loop', '0',
+                 '%s*.%s' % (self.temp_prefix, self.frame_format)]
+                + self.output_args)
+
+
+# Taken directly from jakevdp's JSAnimation package at
+# http://github.com/jakevdp/JSAnimation
+def _included_frames(paths, frame_format):
+    """paths should be a list of Paths"""
+    return INCLUDED_FRAMES.format(Nframes=len(paths),
+                                  frame_dir=paths[0].parent,
+                                  frame_format=frame_format)
+
+
+def _embedded_frames(frame_list, frame_format):
+    """frame_list should be a list of base64-encoded png files"""
+    template = '  frames[{0}] = "data:image/{1};base64,{2}"\n'
+    return "\n" + "".join(
+        template.format(i, frame_format, frame_data.replace('\n', '\\\n'))
+        for i, frame_data in enumerate(frame_list))
+
+
+@writers.register('html')
+class HTMLWriter(FileMovieWriter):
+    supported_formats = ['png', 'jpeg', 'tiff', 'svg']
+    args_key = 'animation.html_args'
+
+    @classmethod
+    def isAvailable(cls):
+        return True
+
+    def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None,
+                 metadata=None, embed_frames=False, default_mode='loop',
+                 embed_limit=None):
+        self.embed_frames = embed_frames
+        self.default_mode = default_mode.lower()
+
+        # Save embed limit, which is given in MB
+        if embed_limit is None:
+            self._bytes_limit = rcParams['animation.embed_limit']
+        else:
+            self._bytes_limit = embed_limit
+
+        # Convert from MB to bytes
+        self._bytes_limit *= 1024 * 1024
+
+        cbook._check_in_list(['loop', 'once', 'reflect'],
+                             default_mode=self.default_mode)
+
+        super().__init__(fps, codec, bitrate, extra_args, metadata)
+
+    def setup(self, fig, outfile, dpi, frame_dir=None):
+        outfile = Path(outfile)
+        cbook._check_in_list(['.html', '.htm'],
+                             outfile_extension=outfile.suffix)
+
+        self._saved_frames = []
+        self._total_bytes = 0
+        self._hit_limit = False
+
+        if not self.embed_frames:
+            if frame_dir is None:
+                frame_dir = outfile.with_name(outfile.stem + '_frames')
+            frame_dir.mkdir(parents=True, exist_ok=True)
+            frame_prefix = frame_dir / 'frame'
+        else:
+            frame_prefix = None
+
+        super().setup(fig, outfile, dpi, frame_prefix, clear_temp=False)
+
+    def grab_frame(self, **savefig_kwargs):
+        if self.embed_frames:
+            # Just stop processing if we hit the limit
+            if self._hit_limit:
+                return
+            f = BytesIO()
+            self.fig.savefig(f, format=self.frame_format,
+                             dpi=self.dpi, **savefig_kwargs)
+            imgdata64 = base64.encodebytes(f.getvalue()).decode('ascii')
+            self._total_bytes += len(imgdata64)
+            if self._total_bytes >= self._bytes_limit:
+                _log.warning(
+                    "Animation size has reached %s bytes, exceeding the limit "
+                    "of %s. If you're sure you want a larger animation "
+                    "embedded, set the animation.embed_limit rc parameter to "
+                    "a larger value (in MB). This and further frames will be "
+                    "dropped.", self._total_bytes, self._bytes_limit)
+                self._hit_limit = True
+            else:
+                self._saved_frames.append(imgdata64)
+        else:
+            return super().grab_frame(**savefig_kwargs)
+
+    def finish(self):
+        # save the frames to an html file
+        if self.embed_frames:
+            fill_frames = _embedded_frames(self._saved_frames,
+                                           self.frame_format)
+            Nframes = len(self._saved_frames)
+        else:
+            # temp names is filled by FileMovieWriter
+            fill_frames = _included_frames(self._temp_paths, self.frame_format)
+            Nframes = len(self._temp_paths)
+        mode_dict = dict(once_checked='',
+                         loop_checked='',
+                         reflect_checked='')
+        mode_dict[self.default_mode + '_checked'] = 'checked'
+
+        interval = 1000 // self.fps
+
+        with open(self.outfile, 'w') as of:
+            of.write(JS_INCLUDE + STYLE_INCLUDE)
+            of.write(DISPLAY_TEMPLATE.format(id=uuid.uuid4().hex,
+                                             Nframes=Nframes,
+                                             fill_frames=fill_frames,
+                                             interval=interval,
+                                             **mode_dict))
+
+
+class Animation:
+    '''This class wraps the creation of an animation using matplotlib.
+
+    It is only a base class which should be subclassed to provide
+    needed behavior.
+
+    This class is not typically used directly.
+
+    Parameters
+    ----------
+    fig : `~matplotlib.figure.Figure`
+       The figure object that is used to get draw, resize, and any
+       other needed events.
+
+    event_source : object, optional
+       A class that can run a callback when desired events
+       are generated, as well as be stopped and started.
+
+       Examples include timers (see :class:`TimedAnimation`) and file
+       system notifications.
+
+    blit : bool, optional
+       controls whether blitting is used to optimize drawing.  Defaults
+       to ``False``.
+
+    See Also
+    --------
+    FuncAnimation,  ArtistAnimation
+
+    '''
+    def __init__(self, fig, event_source=None, blit=False):
+        self._fig = fig
+        # Disables blitting for backends that don't support it.  This
+        # allows users to request it if available, but still have a
+        # fallback that works if it is not.
+        self._blit = blit and fig.canvas.supports_blit
+
+        # These are the basics of the animation.  The frame sequence represents
+        # information for each frame of the animation and depends on how the
+        # drawing is handled by the subclasses. The event source fires events
+        # that cause the frame sequence to be iterated.
+        self.frame_seq = self.new_frame_seq()
+        self.event_source = event_source
+
+        # Instead of starting the event source now, we connect to the figure's
+        # draw_event, so that we only start once the figure has been drawn.
+        self._first_draw_id = fig.canvas.mpl_connect('draw_event', self._start)
+
+        # Connect to the figure's close_event so that we don't continue to
+        # fire events and try to draw to a deleted figure.
+        self._close_id = self._fig.canvas.mpl_connect('close_event',
+                                                      self._stop)
+        if self._blit:
+            self._setup_blit()
+
+    def _start(self, *args):
+        '''
+        Starts interactive animation. Adds the draw frame command to the GUI
+        handler, calls show to start the event loop.
+        '''
+        # First disconnect our draw event handler
+        self._fig.canvas.mpl_disconnect(self._first_draw_id)
+        self._first_draw_id = None  # So we can check on save
+
+        # Now do any initial draw
+        self._init_draw()
+
+        # Add our callback for stepping the animation and
+        # actually start the event_source.
+        self.event_source.add_callback(self._step)
+        self.event_source.start()
+
+    def _stop(self, *args):
+        # On stop we disconnect all of our events.
+        if self._blit:
+            self._fig.canvas.mpl_disconnect(self._resize_id)
+        self._fig.canvas.mpl_disconnect(self._close_id)
+        self.event_source.remove_callback(self._step)
+        self.event_source = None
+
+    def save(self, filename, writer=None, fps=None, dpi=None, codec=None,
+             bitrate=None, extra_args=None, metadata=None, extra_anim=None,
+             savefig_kwargs=None, *, progress_callback=None):
+        """
+        Save the animation as a movie file by drawing every frame.
+
+        Parameters
+        ----------
+        filename : str
+            The output filename, e.g., :file:`mymovie.mp4`.
+
+        writer : :class:`MovieWriter` or str, optional
+            A `MovieWriter` instance to use or a key that identifies a
+            class to use, such as 'ffmpeg'. If ``None``, defaults to
+            :rc:`animation.writer` = 'ffmpeg'.
+
+        fps : number, optional
+           Frames per second in the movie. Defaults to ``None``, which will use
+           the animation's specified interval to set the frames per second.
+
+        dpi : number, optional
+           Controls the dots per inch for the movie frames.  This combined with
+           the figure's size in inches controls the size of the movie.  If
+           ``None``, defaults to :rc:`savefig.dpi`.
+
+        codec : str, optional
+           The video codec to be used. Not all codecs are supported
+           by a given :class:`MovieWriter`. If ``None``, default to
+           :rc:`animation.codec` = 'h264'.
+
+        bitrate : number, optional
+           Specifies the number of bits used per second in the compressed
+           movie, in kilobits per second. A higher number means a higher
+           quality movie, but at the cost of increased file size. If ``None``,
+           defaults to :rc:`animation.bitrate` = -1.
+
+        extra_args : list, optional
+           List of extra string arguments to be passed to the underlying movie
+           utility. If ``None``, defaults to :rc:`animation.extra_args`.
+
+        metadata : Dict[str, str], optional
+           Dictionary of keys and values for metadata to include in
+           the output file. Some keys that may be of use include:
+           title, artist, genre, subject, copyright, srcform, comment.
+
+        extra_anim : list, optional
+           Additional `Animation` objects that should be included
+           in the saved movie file. These need to be from the same
+           `matplotlib.figure.Figure` instance. Also, animation frames will
+           just be simply combined, so there should be a 1:1 correspondence
+           between the frames from the different animations.
+
+        savefig_kwargs : dict, optional
+           Is a dictionary containing keyword arguments to be passed
+           on to the `savefig` command which is called repeatedly to
+           save the individual frames.
+
+        progress_callback : function, optional
+            A callback function that will be called for every frame to notify
+            the saving progress. It must have the signature ::
+
+                def func(current_frame: int, total_frames: int) -> Any
+
+            where *current_frame* is the current frame number and
+            *total_frames* is the total number of frames to be saved.
+            *total_frames* is set to None, if the total number of frames can
+            not be determined. Return values may exist but are ignored.
+
+            Example code to write the progress to stdout::
+
+                progress_callback =\
+                    lambda i, n: print(f'Saving frame {i} of {n}')
+
+        Notes
+        -----
+        *fps*, *codec*, *bitrate*, *extra_args* and *metadata* are used to
+        construct a `.MovieWriter` instance and can only be passed if
+        *writer* is a string.  If they are passed as non-*None* and *writer*
+        is a `.MovieWriter`, a `RuntimeError` will be raised.
+
+        """
+        # If the writer is None, use the rc param to find the name of the one
+        # to use
+        if writer is None:
+            writer = rcParams['animation.writer']
+        elif (not isinstance(writer, str) and
+              any(arg is not None
+                  for arg in (fps, codec, bitrate, extra_args, metadata))):
+            raise RuntimeError('Passing in values for arguments '
+                               'fps, codec, bitrate, extra_args, or metadata '
+                               'is not supported when writer is an existing '
+                               'MovieWriter instance. These should instead be '
+                               'passed as arguments when creating the '
+                               'MovieWriter instance.')
+
+        if savefig_kwargs is None:
+            savefig_kwargs = {}
+
+        # Need to disconnect the first draw callback, since we'll be doing
+        # draws. Otherwise, we'll end up starting the animation.
+        if self._first_draw_id is not None:
+            self._fig.canvas.mpl_disconnect(self._first_draw_id)
+            reconnect_first_draw = True
+        else:
+            reconnect_first_draw = False
+
+        if fps is None and hasattr(self, '_interval'):
+            # Convert interval in ms to frames per second
+            fps = 1000. / self._interval
+
+        # Re-use the savefig DPI for ours if none is given
+        if dpi is None:
+            dpi = rcParams['savefig.dpi']
+        if dpi == 'figure':
+            dpi = self._fig.dpi
+
+        if codec is None:
+            codec = rcParams['animation.codec']
+
+        if bitrate is None:
+            bitrate = rcParams['animation.bitrate']
+
+        all_anim = [self]
+        if extra_anim is not None:
+            all_anim.extend(anim
+                            for anim
+                            in extra_anim if anim._fig is self._fig)
+
+        # If we have the name of a writer, instantiate an instance of the
+        # registered class.
+        if isinstance(writer, str):
+            if writers.is_available(writer):
+                writer = writers[writer](fps, codec, bitrate,
+                                         extra_args=extra_args,
+                                         metadata=metadata)
+            else:
+                alt_writer = next(writers, None)
+                if alt_writer is None:
+                    raise ValueError("Cannot save animation: no writers are "
+                                     "available. Please install ffmpeg to "
+                                     "save animations.")
+                _log.warning("MovieWriter %s unavailable; trying to use %s "
+                             "instead.", writer, alt_writer)
+                writer = alt_writer(
+                    fps, codec, bitrate,
+                    extra_args=extra_args, metadata=metadata)
+        _log.info('Animation.save using %s', type(writer))
+
+        if 'bbox_inches' in savefig_kwargs:
+            _log.warning("Warning: discarding the 'bbox_inches' argument in "
+                         "'savefig_kwargs' as it may cause frame size "
+                         "to vary, which is inappropriate for animation.")
+            savefig_kwargs.pop('bbox_inches')
+
+        # Create a new sequence of frames for saved data. This is different
+        # from new_frame_seq() to give the ability to save 'live' generated
+        # frame information to be saved later.
+        # TODO: Right now, after closing the figure, saving a movie won't work
+        # since GUI widgets are gone. Either need to remove extra code to
+        # allow for this non-existent use case or find a way to make it work.
+        with rc_context():
+            if rcParams['savefig.bbox'] == 'tight':
+                _log.info("Disabling savefig.bbox = 'tight', as it may cause "
+                          "frame size to vary, which is inappropriate for "
+                          "animation.")
+                rcParams['savefig.bbox'] = None
+            with writer.saving(self._fig, filename, dpi):
+                for anim in all_anim:
+                    # Clear the initial frame
+                    anim._init_draw()
+                frame_number = 0
+                # TODO: Currently only FuncAnimation has a save_count
+                #       attribute. Can we generalize this to all Animations?
+                save_count_list = [getattr(a, 'save_count', None)
+                                   for a in all_anim]
+                if None in save_count_list:
+                    total_frames = None
+                else:
+                    total_frames = sum(save_count_list)
+                for data in zip(*[a.new_saved_frame_seq() for a in all_anim]):
+                    for anim, d in zip(all_anim, data):
+                        # TODO: See if turning off blit is really necessary
+                        anim._draw_next_frame(d, blit=False)
+                        if progress_callback is not None:
+                            progress_callback(frame_number, total_frames)
+                            frame_number += 1
+                    writer.grab_frame(**savefig_kwargs)
+
+        # Reconnect signal for first draw if necessary
+        if reconnect_first_draw:
+            self._first_draw_id = self._fig.canvas.mpl_connect('draw_event',
+                                                               self._start)
+
+    def _step(self, *args):
+        '''
+        Handler for getting events. By default, gets the next frame in the
+        sequence and hands the data off to be drawn.
+        '''
+        # Returns True to indicate that the event source should continue to
+        # call _step, until the frame sequence reaches the end of iteration,
+        # at which point False will be returned.
+        try:
+            framedata = next(self.frame_seq)
+            self._draw_next_frame(framedata, self._blit)
+            return True
+        except StopIteration:
+            return False
+
+    def new_frame_seq(self):
+        """Return a new sequence of frame information."""
+        # Default implementation is just an iterator over self._framedata
+        return iter(self._framedata)
+
+    def new_saved_frame_seq(self):
+        """Return a new sequence of saved/cached frame information."""
+        # Default is the same as the regular frame sequence
+        return self.new_frame_seq()
+
+    def _draw_next_frame(self, framedata, blit):
+        # Breaks down the drawing of the next frame into steps of pre- and
+        # post- draw, as well as the drawing of the frame itself.
+        self._pre_draw(framedata, blit)
+        self._draw_frame(framedata)
+        self._post_draw(framedata, blit)
+
+    def _init_draw(self):
+        # Initial draw to clear the frame. Also used by the blitting code
+        # when a clean base is required.
+        pass
+
+    def _pre_draw(self, framedata, blit):
+        # Perform any cleaning or whatnot before the drawing of the frame.
+        # This default implementation allows blit to clear the frame.
+        if blit:
+            self._blit_clear(self._drawn_artists, self._blit_cache)
+
+    def _draw_frame(self, framedata):
+        # Performs actual drawing of the frame.
+        raise NotImplementedError('Needs to be implemented by subclasses to'
+                                  ' actually make an animation.')
+
+    def _post_draw(self, framedata, blit):
+        # After the frame is rendered, this handles the actual flushing of
+        # the draw, which can be a direct draw_idle() or make use of the
+        # blitting.
+        if blit and self._drawn_artists:
+            self._blit_draw(self._drawn_artists, self._blit_cache)
+        else:
+            self._fig.canvas.draw_idle()
+
+    # The rest of the code in this class is to facilitate easy blitting
+    def _blit_draw(self, artists, bg_cache):
+        # Handles blitted drawing, which renders only the artists given instead
+        # of the entire figure.
+        updated_ax = []
+
+        # Enumerate artists to cache axes' backgrounds. We do not draw
+        # artists yet to not cache foreground from plots with shared axes
+        for a in artists:
+            # If we haven't cached the background for this axes object, do
+            # so now. This might not always be reliable, but it's an attempt
+            # to automate the process.
+            if a.axes not in bg_cache:
+                bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
+
+        # Make a separate pass to draw foreground
+        for a in artists:
+            a.axes.draw_artist(a)
+            updated_ax.append(a.axes)
+
+        # After rendering all the needed artists, blit each axes individually.
+        for ax in set(updated_ax):
+            ax.figure.canvas.blit(ax.bbox)
+
+    def _blit_clear(self, artists, bg_cache):
+        # Get a list of the axes that need clearing from the artists that
+        # have been drawn. Grab the appropriate saved background from the
+        # cache and restore.
+        axes = {a.axes for a in artists}
+        for a in axes:
+            if a in bg_cache:
+                a.figure.canvas.restore_region(bg_cache[a])
+
+    def _setup_blit(self):
+        # Setting up the blit requires: a cache of the background for the
+        # axes
+        self._blit_cache = dict()
+        self._drawn_artists = []
+        for ax in self._fig.axes:
+            ax.callbacks.connect('xlim_changed',
+                                 lambda ax: self._blit_cache.pop(ax, None))
+            ax.callbacks.connect('ylim_changed',
+                                 lambda ax: self._blit_cache.pop(ax, None))
+        self._resize_id = self._fig.canvas.mpl_connect('resize_event',
+                                                       self._handle_resize)
+        self._post_draw(None, self._blit)
+
+    def _handle_resize(self, *args):
+        # On resize, we need to disable the resize event handling so we don't
+        # get too many events. Also stop the animation events, so that
+        # we're paused. Reset the cache and re-init. Set up an event handler
+        # to catch once the draw has actually taken place.
+        self._fig.canvas.mpl_disconnect(self._resize_id)
+        self.event_source.stop()
+        self._blit_cache.clear()
+        self._init_draw()
+        self._resize_id = self._fig.canvas.mpl_connect('draw_event',
+                                                       self._end_redraw)
+
+    def _end_redraw(self, evt):
+        # Now that the redraw has happened, do the post draw flushing and
+        # blit handling. Then re-enable all of the original events.
+        self._post_draw(None, False)
+        self.event_source.start()
+        self._fig.canvas.mpl_disconnect(self._resize_id)
+        self._resize_id = self._fig.canvas.mpl_connect('resize_event',
+                                                       self._handle_resize)
+
+    def to_html5_video(self, embed_limit=None):
+        """
+        Convert the animation to an HTML5 ``<video>`` tag.
+
+        This saves the animation as an h264 video, encoded in base64
+        directly into the HTML5 video tag. This respects the rc parameters
+        for the writer as well as the bitrate. This also makes use of the
+        ``interval`` to control the speed, and uses the ``repeat``
+        parameter to decide whether to loop.
+
+        Parameters
+        ----------
+        embed_limit : float, optional
+            Limit, in MB, of the returned animation. No animation is created
+            if the limit is exceeded.
+            Defaults to :rc:`animation.embed_limit` = 20.0.
+
+        Returns
+        -------
+        video_tag : str
+            An HTML5 video tag with the animation embedded as base64 encoded
+            h264 video.
+            If the *embed_limit* is exceeded, this returns the string
+            "Video too large to embed."
+        """
+        VIDEO_TAG = r'''<video {size} {options}>
+  <source type="video/mp4" src="data:video/mp4;base64,{video}">
+  Your browser does not support the video tag.
+</video>'''
+        # Cache the rendering of the video as HTML
+        if not hasattr(self, '_base64_video'):
+            # Save embed limit, which is given in MB
+            if embed_limit is None:
+                embed_limit = rcParams['animation.embed_limit']
+
+            # Convert from MB to bytes
+            embed_limit *= 1024 * 1024
+
+            # Can't open a NamedTemporaryFile twice on Windows, so use a
+            # TemporaryDirectory instead.
+            with TemporaryDirectory() as tmpdir:
+                path = Path(tmpdir, "temp.m4v")
+                # We create a writer manually so that we can get the
+                # appropriate size for the tag
+                Writer = writers[rcParams['animation.writer']]
+                writer = Writer(codec='h264',
+                                bitrate=rcParams['animation.bitrate'],
+                                fps=1000. / self._interval)
+                self.save(str(path), writer=writer)
+                # Now open and base64 encode.
+                vid64 = base64.encodebytes(path.read_bytes())
+
+            vid_len = len(vid64)
+            if vid_len >= embed_limit:
+                _log.warning(
+                    "Animation movie is %s bytes, exceeding the limit of %s. "
+                    "If you're sure you want a large animation embedded, set "
+                    "the animation.embed_limit rc parameter to a larger value "
+                    "(in MB).", vid_len, embed_limit)
+            else:
+                self._base64_video = vid64.decode('ascii')
+                self._video_size = 'width="{}" height="{}"'.format(
+                        *writer.frame_size)
+
+        # If we exceeded the size, this attribute won't exist
+        if hasattr(self, '_base64_video'):
+            # Default HTML5 options are to autoplay and display video controls
+            options = ['controls', 'autoplay']
+
+            # If we're set to repeat, make it loop
+            if hasattr(self, 'repeat') and self.repeat:
+                options.append('loop')
+
+            return VIDEO_TAG.format(video=self._base64_video,
+                                    size=self._video_size,
+                                    options=' '.join(options))
+        else:
+            return 'Video too large to embed.'
+
+    def to_jshtml(self, fps=None, embed_frames=True, default_mode=None):
+        """Generate HTML representation of the animation"""
+        if fps is None and hasattr(self, '_interval'):
+            # Convert interval in ms to frames per second
+            fps = 1000 / self._interval
+
+        # If we're not given a default mode, choose one base on the value of
+        # the repeat attribute
+        if default_mode is None:
+            default_mode = 'loop' if self.repeat else 'once'
+
+        if not hasattr(self, "_html_representation"):
+            # Can't open a NamedTemporaryFile twice on Windows, so use a
+            # TemporaryDirectory instead.
+            with TemporaryDirectory() as tmpdir:
+                path = Path(tmpdir, "temp.html")
+                writer = HTMLWriter(fps=fps,
+                                    embed_frames=embed_frames,
+                                    default_mode=default_mode)
+                self.save(str(path), writer=writer)
+                self._html_representation = path.read_text()
+
+        return self._html_representation
+
+    def _repr_html_(self):
+        '''IPython display hook for rendering.'''
+        fmt = rcParams['animation.html']
+        if fmt == 'html5':
+            return self.to_html5_video()
+        elif fmt == 'jshtml':
+            return self.to_jshtml()
+
+
+class TimedAnimation(Animation):
+    ''':class:`Animation` subclass for time-based animation.
+
+    A new frame is drawn every *interval* milliseconds.
+
+    Parameters
+    ----------
+    fig : `~matplotlib.figure.Figure`
+       The figure object that is used to get draw, resize, and any
+       other needed events.
+
+    interval : number, optional
+       Delay between frames in milliseconds.  Defaults to 200.
+
+    repeat_delay : number, optional
+        If the animation in repeated, adds a delay in milliseconds
+        before repeating the animation.  Defaults to ``None``.
+
+    repeat : bool, optional
+        Controls whether the animation should repeat when the sequence
+        of frames is completed.  Defaults to ``True``.
+
+    blit : bool, optional
+        Controls whether blitting is used to optimize drawing.  Defaults
+        to ``False``.
+
+    '''
+    def __init__(self, fig, interval=200, repeat_delay=None, repeat=True,
+                 event_source=None, *args, **kwargs):
+        # Store the timing information
+        self._interval = interval
+        self._repeat_delay = repeat_delay
+        self.repeat = repeat
+
+        # If we're not given an event source, create a new timer. This permits
+        # sharing timers between animation objects for syncing animations.
+        if event_source is None:
+            event_source = fig.canvas.new_timer()
+            event_source.interval = self._interval
+
+        Animation.__init__(self, fig, event_source=event_source,
+                           *args, **kwargs)
+
+    def _step(self, *args):
+        '''
+        Handler for getting events.
+        '''
+        # Extends the _step() method for the Animation class.  If
+        # Animation._step signals that it reached the end and we want to
+        # repeat, we refresh the frame sequence and return True. If
+        # _repeat_delay is set, change the event_source's interval to our loop
+        # delay and set the callback to one which will then set the interval
+        # back.
+        still_going = Animation._step(self, *args)
+        if not still_going and self.repeat:
+            self._init_draw()
+            self.frame_seq = self.new_frame_seq()
+            if self._repeat_delay:
+                self.event_source.remove_callback(self._step)
+                self.event_source.add_callback(self._loop_delay)
+                self.event_source.interval = self._repeat_delay
+                return True
+            else:
+                return Animation._step(self, *args)
+        else:
+            return still_going
+
+    def _stop(self, *args):
+        # If we stop in the middle of a loop delay (which is relatively likely
+        # given the potential pause here, remove the loop_delay callback as
+        # well.
+        self.event_source.remove_callback(self._loop_delay)
+        Animation._stop(self)
+
+    def _loop_delay(self, *args):
+        # Reset the interval and change callbacks after the delay.
+        self.event_source.remove_callback(self._loop_delay)
+        self.event_source.interval = self._interval
+        self.event_source.add_callback(self._step)
+        Animation._step(self)
+
+
+class ArtistAnimation(TimedAnimation):
+    '''Animation using a fixed set of `Artist` objects.
+
+    Before creating an instance, all plotting should have taken place
+    and the relevant artists saved.
+
+    Parameters
+    ----------
+    fig : `~matplotlib.figure.Figure`
+       The figure object that is used to get draw, resize, and any
+       other needed events.
+
+    artists : list
+        Each list entry a collection of artists that represent what
+        needs to be enabled on each frame. These will be disabled for
+        other frames.
+
+    interval : number, optional
+       Delay between frames in milliseconds.  Defaults to 200.
+
+    repeat_delay : number, optional
+        If the animation in repeated, adds a delay in milliseconds
+        before repeating the animation.  Defaults to ``None``.
+
+    repeat : bool, optional
+        Controls whether the animation should repeat when the sequence
+        of frames is completed. Defaults to ``True``.
+
+    blit : bool, optional
+        Controls whether blitting is used to optimize drawing.  Defaults
+        to ``False``.
+
+    '''
+    def __init__(self, fig, artists, *args, **kwargs):
+        # Internal list of artists drawn in the most recent frame.
+        self._drawn_artists = []
+
+        # Use the list of artists as the framedata, which will be iterated
+        # over by the machinery.
+        self._framedata = artists
+        TimedAnimation.__init__(self, fig, *args, **kwargs)
+
+    def _init_draw(self):
+        # Make all the artists involved in *any* frame invisible
+        figs = set()
+        for f in self.new_frame_seq():
+            for artist in f:
+                artist.set_visible(False)
+                artist.set_animated(self._blit)
+                # Assemble a list of unique figures that need flushing
+                if artist.get_figure() not in figs:
+                    figs.add(artist.get_figure())
+
+        # Flush the needed figures
+        for fig in figs:
+            fig.canvas.draw_idle()
+
+    def _pre_draw(self, framedata, blit):
+        '''
+        Clears artists from the last frame.
+        '''
+        if blit:
+            # Let blit handle clearing
+            self._blit_clear(self._drawn_artists, self._blit_cache)
+        else:
+            # Otherwise, make all the artists from the previous frame invisible
+            for artist in self._drawn_artists:
+                artist.set_visible(False)
+
+    def _draw_frame(self, artists):
+        # Save the artists that were passed in as framedata for the other
+        # steps (esp. blitting) to use.
+        self._drawn_artists = artists
+
+        # Make all the artists from the current frame visible
+        for artist in artists:
+            artist.set_visible(True)
+
+
+class FuncAnimation(TimedAnimation):
+    """
+    Makes an animation by repeatedly calling a function *func*.
+
+    Parameters
+    ----------
+    fig : `~matplotlib.figure.Figure`
+       The figure object that is used to get draw, resize, and any
+       other needed events.
+
+    func : callable
+       The function to call at each frame.  The first argument will
+       be the next value in *frames*.   Any additional positional
+       arguments can be supplied via the *fargs* parameter.
+
+       The required signature is::
+
+          def func(frame, *fargs) -> iterable_of_artists
+
+       If ``blit == True``, *func* must return an iterable of all artists
+       that were modified or created. This information is used by the blitting
+       algorithm to determine which parts of the figure have to be updated.
+       The return value is unused if ``blit == False`` and may be omitted in
+       that case.
+
+    frames : iterable, int, generator function, or None, optional
+        Source of data to pass *func* and each frame of the animation
+
+        - If an iterable, then simply use the values provided.  If the
+          iterable has a length, it will override the *save_count* kwarg.
+
+        - If an integer, then equivalent to passing ``range(frames)``
+
+        - If a generator function, then must have the signature::
+
+             def gen_function() -> obj
+
+        - If *None*, then equivalent to passing ``itertools.count``.
+
+        In all of these cases, the values in *frames* is simply passed through
+        to the user-supplied *func* and thus can be of any type.
+
+    init_func : callable, optional
+       A function used to draw a clear frame. If not given, the
+       results of drawing from the first item in the frames sequence
+       will be used. This function will be called once before the
+       first frame.
+
+       The required signature is::
+
+          def init_func() -> iterable_of_artists
+
+       If ``blit == True``, *init_func* must return an iterable of artists
+       to be re-drawn. This information is used by the blitting
+       algorithm to determine which parts of the figure have to be updated.
+       The return value is unused if ``blit == False`` and may be omitted in
+       that case.
+
+    fargs : tuple or None, optional
+       Additional arguments to pass to each call to *func*.
+
+    save_count : int, default: 100
+        Fallback for the number of values from *frames* to cache. This is
+        only used if the number of frames cannot be inferred from *frames*,
+        i.e. when it's an iterator without length or a generator.
+
+    interval : number, optional
+       Delay between frames in milliseconds.  Defaults to 200.
+
+    repeat_delay : number, optional
+       If the animation in repeated, adds a delay in milliseconds
+       before repeating the animation.  Defaults to *None*.
+
+    repeat : bool, optional
+       Controls whether the animation should repeat when the sequence
+       of frames is completed.  Defaults to *True*.
+
+    blit : bool, optional
+       Controls whether blitting is used to optimize drawing. Note: when using
+       blitting any animated artists will be drawn according to their zorder.
+       However, they will be drawn on top of any previous artists, regardless
+       of their zorder.  Defaults to *False*.
+
+    cache_frame_data : bool, optional
+       Controls whether frame data is cached. Defaults to *True*.
+       Disabling cache might be helpful when frames contain large objects.
+    """
+
+    def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
+                 save_count=None, *, cache_frame_data=True, **kwargs):
+        if fargs:
+            self._args = fargs
+        else:
+            self._args = ()
+        self._func = func
+        self._init_func = init_func
+
+        # Amount of framedata to keep around for saving movies. This is only
+        # used if we don't know how many frames there will be: in the case
+        # of no generator or in the case of a callable.
+        self.save_count = save_count
+        # Set up a function that creates a new iterable when needed. If nothing
+        # is passed in for frames, just use itertools.count, which will just
+        # keep counting from 0. A callable passed in for frames is assumed to
+        # be a generator. An iterable will be used as is, and anything else
+        # will be treated as a number of frames.
+        if frames is None:
+            self._iter_gen = itertools.count
+        elif callable(frames):
+            self._iter_gen = frames
+        elif np.iterable(frames):
+            if kwargs.get('repeat', True):
+                def iter_frames(frames=frames):
+                    while True:
+                        this, frames = itertools.tee(frames, 2)
+                        yield from this
+                self._iter_gen = iter_frames
+            else:
+                self._iter_gen = lambda: iter(frames)
+            if hasattr(frames, '__len__'):
+                self.save_count = len(frames)
+        else:
+            self._iter_gen = lambda: iter(range(frames))
+            self.save_count = frames
+
+        if self.save_count is None:
+            # If we're passed in and using the default, set save_count to 100.
+            self.save_count = 100
+        else:
+            # itertools.islice returns an error when passed a numpy int instead
+            # of a native python int (http://bugs.python.org/issue30537).
+            # As a workaround, convert save_count to a native python int.
+            self.save_count = int(self.save_count)
+
+        self._cache_frame_data = cache_frame_data
+
+        # Needs to be initialized so the draw functions work without checking
+        self._save_seq = []
+
+        TimedAnimation.__init__(self, fig, **kwargs)
+
+        # Need to reset the saved seq, since right now it will contain data
+        # for a single frame from init, which is not what we want.
+        self._save_seq = []
+
+    def new_frame_seq(self):
+        # Use the generating function to generate a new frame sequence
+        return self._iter_gen()
+
+    def new_saved_frame_seq(self):
+        # Generate an iterator for the sequence of saved data. If there are
+        # no saved frames, generate a new frame sequence and take the first
+        # save_count entries in it.
+        if self._save_seq:
+            # While iterating we are going to update _save_seq
+            # so make a copy to safely iterate over
+            self._old_saved_seq = list(self._save_seq)
+            return iter(self._old_saved_seq)
+        else:
+            if self.save_count is not None:
+                return itertools.islice(self.new_frame_seq(), self.save_count)
+
+            else:
+                frame_seq = self.new_frame_seq()
+
+                def gen():
+                    try:
+                        for _ in range(100):
+                            yield next(frame_seq)
+                    except StopIteration:
+                        pass
+                    else:
+                        cbook.warn_deprecated(
+                            "2.2", message="FuncAnimation.save has truncated "
+                            "your animation to 100 frames.  In the future, no "
+                            "such truncation will occur; please pass "
+                            "'save_count' accordingly.")
+
+                return gen()
+
+    def _init_draw(self):
+        # Initialize the drawing either using the given init_func or by
+        # calling the draw function with the first item of the frame sequence.
+        # For blitting, the init_func should return a sequence of modified
+        # artists.
+        if self._init_func is None:
+            self._draw_frame(next(self.new_frame_seq()))
+
+        else:
+            self._drawn_artists = self._init_func()
+            if self._blit:
+                if self._drawn_artists is None:
+                    raise RuntimeError('The init_func must return a '
+                                       'sequence of Artist objects.')
+                for a in self._drawn_artists:
+                    a.set_animated(self._blit)
+        self._save_seq = []
+
+    def _draw_frame(self, framedata):
+        if self._cache_frame_data:
+            # Save the data for potential saving of movies.
+            self._save_seq.append(framedata)
+
+        # Make sure to respect save_count (keep only the last save_count
+        # around)
+        self._save_seq = self._save_seq[-self.save_count:]
+
+        # Call the func with framedata and args. If blitting is desired,
+        # func needs to return a sequence of any artists that were modified.
+        self._drawn_artists = self._func(framedata, *self._args)
+        if self._blit:
+            if self._drawn_artists is None:
+                raise RuntimeError('The animation function must return a '
+                                   'sequence of Artist objects.')
+            self._drawn_artists = sorted(self._drawn_artists,
+                                         key=lambda x: x.get_zorder())
+
+            for a in self._drawn_artists:
+                a.set_animated(self._blit)

+ 1633 - 0
venv/lib/python3.8/site-packages/matplotlib/artist.py

@@ -0,0 +1,1633 @@
+from collections import OrderedDict, namedtuple
+from functools import wraps
+import inspect
+import logging
+from numbers import Number
+import re
+import warnings
+
+import numpy as np
+
+import matplotlib
+from . import cbook, docstring, rcParams
+from .path import Path
+from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox,
+                         TransformedPatchPath, TransformedPath)
+
+_log = logging.getLogger(__name__)
+
+
+def allow_rasterization(draw):
+    """
+    Decorator for Artist.draw method. Provides routines
+    that run before and after the draw call. The before and after functions
+    are useful for changing artist-dependent renderer attributes or making
+    other setup function calls, such as starting and flushing a mixed-mode
+    renderer.
+    """
+
+    # the axes class has a second argument inframe for its draw method.
+    @wraps(draw)
+    def draw_wrapper(artist, renderer, *args, **kwargs):
+        try:
+            if artist.get_rasterized():
+                renderer.start_rasterizing()
+            if artist.get_agg_filter() is not None:
+                renderer.start_filter()
+
+            return draw(artist, renderer, *args, **kwargs)
+        finally:
+            if artist.get_agg_filter() is not None:
+                renderer.stop_filter(artist.get_agg_filter())
+            if artist.get_rasterized():
+                renderer.stop_rasterizing()
+
+    draw_wrapper._supports_rasterization = True
+    return draw_wrapper
+
+
+def _stale_axes_callback(self, val):
+    if self.axes:
+        self.axes.stale = val
+
+
+_XYPair = namedtuple("_XYPair", "x y")
+
+
+class Artist:
+    """
+    Abstract base class for objects that render into a FigureCanvas.
+
+    Typically, all visible elements in a figure are subclasses of Artist.
+    """
+    @cbook.deprecated("3.1")
+    @property
+    def aname(self):
+        return 'Artist'
+
+    zorder = 0
+    # order of precedence when bulk setting/updating properties
+    # via update.  The keys should be property names and the values
+    # integers
+    _prop_order = dict(color=-1)
+
+    def __init__(self):
+        self._stale = True
+        self.stale_callback = None
+        self._axes = None
+        self.figure = None
+
+        self._transform = None
+        self._transformSet = False
+        self._visible = True
+        self._animated = False
+        self._alpha = None
+        self.clipbox = None
+        self._clippath = None
+        self._clipon = True
+        self._label = ''
+        self._picker = None
+        self._contains = None
+        self._rasterized = None
+        self._agg_filter = None
+        self._mouseover = False
+        self.eventson = False  # fire events only if eventson
+        self._oid = 0  # an observer id
+        self._propobservers = {}  # a dict from oids to funcs
+        try:
+            self.axes = None
+        except AttributeError:
+            # Handle self.axes as a read-only property, as in Figure.
+            pass
+        self._remove_method = None
+        self._url = None
+        self._gid = None
+        self._snap = None
+        self._sketch = rcParams['path.sketch']
+        self._path_effects = rcParams['path.effects']
+        self._sticky_edges = _XYPair([], [])
+        self._in_layout = True
+
+    def __getstate__(self):
+        d = self.__dict__.copy()
+        # remove the unpicklable remove method, this will get re-added on load
+        # (by the axes) if the artist lives on an axes.
+        d['stale_callback'] = None
+        return d
+
+    def remove(self):
+        """
+        Remove the artist from the figure if possible.
+
+        The effect will not be visible until the figure is redrawn, e.g.,
+        with `.FigureCanvasBase.draw_idle`.  Call `~.axes.Axes.relim` to
+        update the axes limits if desired.
+
+        Note: `~.axes.Axes.relim` will not see collections even if the
+        collection was added to the axes with *autolim* = True.
+
+        Note: there is no support for removing the artist's legend entry.
+        """
+
+        # There is no method to set the callback.  Instead the parent should
+        # set the _remove_method attribute directly.  This would be a
+        # protected attribute if Python supported that sort of thing.  The
+        # callback has one parameter, which is the child to be removed.
+        if self._remove_method is not None:
+            self._remove_method(self)
+            # clear stale callback
+            self.stale_callback = None
+            _ax_flag = False
+            if hasattr(self, 'axes') and self.axes:
+                # remove from the mouse hit list
+                self.axes._mouseover_set.discard(self)
+                # mark the axes as stale
+                self.axes.stale = True
+                # decouple the artist from the axes
+                self.axes = None
+                _ax_flag = True
+
+            if self.figure:
+                self.figure = None
+                if not _ax_flag:
+                    self.figure = True
+
+        else:
+            raise NotImplementedError('cannot remove artist')
+        # TODO: the fix for the collections relim problem is to move the
+        # limits calculation into the artist itself, including the property of
+        # whether or not the artist should affect the limits.  Then there will
+        # be no distinction between axes.add_line, axes.add_patch, etc.
+        # TODO: add legend support
+
+    def have_units(self):
+        """Return *True* if units are set on any axis."""
+        ax = self.axes
+        return ax and any(axis.have_units() for axis in ax._get_axis_list())
+
+    def convert_xunits(self, x):
+        """
+        Convert *x* using the unit type of the xaxis.
+
+        If the artist is not in contained in an Axes or if the xaxis does not
+        have units, *x* itself is returned.
+        """
+        ax = getattr(self, 'axes', None)
+        if ax is None or ax.xaxis is None:
+            return x
+        return ax.xaxis.convert_units(x)
+
+    def convert_yunits(self, y):
+        """
+        Convert *y* using the unit type of the yaxis.
+
+        If the artist is not in contained in an Axes or if the yaxis does not
+        have units, *y* itself is returned.
+        """
+        ax = getattr(self, 'axes', None)
+        if ax is None or ax.yaxis is None:
+            return y
+        return ax.yaxis.convert_units(y)
+
+    @property
+    def axes(self):
+        """The `~.axes.Axes` instance the artist resides in, or *None*."""
+        return self._axes
+
+    @axes.setter
+    def axes(self, new_axes):
+        if (new_axes is not None and self._axes is not None
+                and new_axes != self._axes):
+            raise ValueError("Can not reset the axes.  You are probably "
+                             "trying to re-use an artist in more than one "
+                             "Axes which is not supported")
+        self._axes = new_axes
+        if new_axes is not None and new_axes is not self:
+            self.stale_callback = _stale_axes_callback
+        return new_axes
+
+    @property
+    def stale(self):
+        """
+        Whether the artist is 'stale' and needs to be re-drawn for the output
+        to match the internal state of the artist.
+        """
+        return self._stale
+
+    @stale.setter
+    def stale(self, val):
+        self._stale = val
+
+        # if the artist is animated it does not take normal part in the
+        # draw stack and is not expected to be drawn as part of the normal
+        # draw loop (when not saving) so do not propagate this change
+        if self.get_animated():
+            return
+
+        if val and self.stale_callback is not None:
+            self.stale_callback(self, val)
+
+    def get_window_extent(self, renderer):
+        """
+        Get the axes bounding box in display space.
+
+        The bounding box' width and height are nonnegative.
+
+        Subclasses should override for inclusion in the bounding box
+        "tight" calculation. Default is to return an empty bounding
+        box at 0, 0.
+
+        Be careful when using this function, the results will not update
+        if the artist window extent of the artist changes.  The extent
+        can change due to any changes in the transform stack, such as
+        changing the axes limits, the figure size, or the canvas used
+        (as is done when saving a figure).  This can lead to unexpected
+        behavior where interactive figures will look fine on the screen,
+        but will save incorrectly.
+        """
+        return Bbox([[0, 0], [0, 0]])
+
+    def _get_clipping_extent_bbox(self):
+        """
+        Return a bbox with the extents of the intersection of the clip_path
+        and clip_box for this artist, or None if both of these are
+        None, or ``get_clip_on`` is False.
+        """
+        bbox = None
+        if self.get_clip_on():
+            clip_box = self.get_clip_box()
+            if clip_box is not None:
+                bbox = clip_box
+            clip_path = self.get_clip_path()
+            if clip_path is not None and bbox is not None:
+                clip_path = clip_path.get_fully_transformed_path()
+                bbox = Bbox.intersection(bbox, clip_path.get_extents())
+        return bbox
+
+    def get_tightbbox(self, renderer):
+        """
+        Like `Artist.get_window_extent`, but includes any clipping.
+
+        Parameters
+        ----------
+        renderer : `.RendererBase` instance
+            renderer that will be used to draw the figures (i.e.
+            ``fig.canvas.get_renderer()``)
+
+        Returns
+        -------
+        bbox : `.Bbox`
+            The enclosing bounding box (in figure pixel co-ordinates).
+        """
+        bbox = self.get_window_extent(renderer)
+        if self.get_clip_on():
+            clip_box = self.get_clip_box()
+            if clip_box is not None:
+                bbox = Bbox.intersection(bbox, clip_box)
+            clip_path = self.get_clip_path()
+            if clip_path is not None and bbox is not None:
+                clip_path = clip_path.get_fully_transformed_path()
+                bbox = Bbox.intersection(bbox, clip_path.get_extents())
+        return bbox
+
+    def add_callback(self, func):
+        """
+        Add a callback function that will be called whenever one of the
+        `.Artist`'s properties changes.
+
+        Parameters
+        ----------
+        func : callable
+            The callback function. It must have the signature::
+
+                def func(artist: Artist) -> Any
+
+            where *artist* is the calling `.Artist`. Return values may exist
+            but are ignored.
+
+        Returns
+        -------
+        oid : int
+            The observer id associated with the callback. This id can be
+            used for removing the callback with `.remove_callback` later.
+
+        See Also
+        --------
+        remove_callback
+        """
+        oid = self._oid
+        self._propobservers[oid] = func
+        self._oid += 1
+        return oid
+
+    def remove_callback(self, oid):
+        """
+        Remove a callback based on its observer id.
+
+        See Also
+        --------
+        add_callback
+        """
+        try:
+            del self._propobservers[oid]
+        except KeyError:
+            pass
+
+    def pchanged(self):
+        """
+        Call all of the registered callbacks.
+
+        This function is triggered internally when a property is changed.
+
+        See Also
+        --------
+        add_callback
+        remove_callback
+        """
+        for oid, func in self._propobservers.items():
+            func(self)
+
+    def is_transform_set(self):
+        """
+        Return whether the Artist has an explicitly set transform.
+
+        This is *True* after `.set_transform` has been called.
+        """
+        return self._transformSet
+
+    def set_transform(self, t):
+        """
+        Set the artist transform.
+
+        Parameters
+        ----------
+        t : `.Transform`
+        """
+        self._transform = t
+        self._transformSet = True
+        self.pchanged()
+        self.stale = True
+
+    def get_transform(self):
+        """Return the `.Transform` instance used by this artist."""
+        if self._transform is None:
+            self._transform = IdentityTransform()
+        elif (not isinstance(self._transform, Transform)
+              and hasattr(self._transform, '_as_mpl_transform')):
+            self._transform = self._transform._as_mpl_transform(self.axes)
+        return self._transform
+
+    def get_children(self):
+        r"""Return a list of the child `.Artist`\s of this `.Artist`."""
+        return []
+
+    def _default_contains(self, mouseevent, figure=None):
+        """
+        Base impl. for checking whether a mouseevent happened in an artist.
+
+        1. If the artist defines a custom checker, use it.
+        2. If the artist figure is known and the event did not occur in that
+           figure (by checking its ``canvas`` attribute), reject it.
+        3. Otherwise, return `None, {}`, indicating that the subclass'
+           implementation should be used.
+
+        Subclasses should start their definition of `contains` as follows:
+
+            inside, info = self._default_contains(mouseevent)
+            if inside is not None:
+                return inside, info
+            # subclass-specific implementation follows
+
+        The `canvas` kwarg is provided for the implementation of
+        `Figure.contains`.
+        """
+        if callable(self._contains):
+            return self._contains(self, mouseevent)
+        if figure is not None and mouseevent.canvas is not figure.canvas:
+            return False, {}
+        return None, {}
+
+    def contains(self, mouseevent):
+        """Test whether the artist contains the mouse event.
+
+        Parameters
+        ----------
+        mouseevent : `matplotlib.backend_bases.MouseEvent`
+
+        Returns
+        -------
+        contains : bool
+            Whether any values are within the radius.
+        details : dict
+            An artist-specific dictionary of details of the event context,
+            such as which points are contained in the pick radius. See the
+            individual Artist subclasses for details.
+
+        See Also
+        --------
+        set_contains, get_contains
+        """
+        inside, info = self._default_contains(mouseevent)
+        if inside is not None:
+            return inside, info
+        _log.warning("%r needs 'contains' method", self.__class__.__name__)
+        return False, {}
+
+    def set_contains(self, picker):
+        """
+        Define a custom contains test for the artist.
+
+        The provided callable replaces the default `.contains` method
+        of the artist.
+
+        Parameters
+        ----------
+        picker : callable
+            A custom picker function to evaluate if an event is within the
+            artist. The function must have the signature::
+
+                def contains(artist: Artist, event: MouseEvent) -> bool, dict
+
+            that returns:
+
+            - a bool indicating if the event is within the artist
+            - a dict of additional information. The dict should at least
+              return the same information as the default ``contains()``
+              implementation of the respective artist, but may provide
+              additional information.
+        """
+        if not callable(picker):
+            raise TypeError("picker is not a callable")
+        self._contains = picker
+
+    def get_contains(self):
+        """
+        Return the custom contains function of the artist if set, or *None*.
+
+        See Also
+        --------
+        set_contains
+        """
+        return self._contains
+
+    def pickable(self):
+        """
+        Return whether the artist is pickable.
+
+        See Also
+        --------
+        set_picker, get_picker, pick
+        """
+        return self.figure is not None and self._picker is not None
+
+    def pick(self, mouseevent):
+        """
+        Process a pick event.
+
+        Each child artist will fire a pick event if *mouseevent* is over
+        the artist and the artist has picker set.
+
+        See Also
+        --------
+        set_picker, get_picker, pickable
+        """
+        # Pick self
+        if self.pickable():
+            picker = self.get_picker()
+            if callable(picker):
+                inside, prop = picker(self, mouseevent)
+            else:
+                inside, prop = self.contains(mouseevent)
+            if inside:
+                self.figure.canvas.pick_event(mouseevent, self, **prop)
+
+        # Pick children
+        for a in self.get_children():
+            # make sure the event happened in the same axes
+            ax = getattr(a, 'axes', None)
+            if (mouseevent.inaxes is None or ax is None
+                    or mouseevent.inaxes == ax):
+                # we need to check if mouseevent.inaxes is None
+                # because some objects associated with an axes (e.g., a
+                # tick label) can be outside the bounding box of the
+                # axes and inaxes will be None
+                # also check that ax is None so that it traverse objects
+                # which do no have an axes property but children might
+                a.pick(mouseevent)
+
+    def set_picker(self, picker):
+        """
+        Define the picking behavior of the artist.
+
+        Parameters
+        ----------
+        picker : None or bool or float or callable
+            This can be one of the following:
+
+            - *None*: Picking is disabled for this artist (default).
+
+            - A boolean: If *True* then picking will be enabled and the
+              artist will fire a pick event if the mouse event is over
+              the artist.
+
+            - A float: If picker is a number it is interpreted as an
+              epsilon tolerance in points and the artist will fire
+              off an event if it's data is within epsilon of the mouse
+              event.  For some artists like lines and patch collections,
+              the artist may provide additional data to the pick event
+              that is generated, e.g., the indices of the data within
+              epsilon of the pick event
+
+            - A function: If picker is callable, it is a user supplied
+              function which determines whether the artist is hit by the
+              mouse event::
+
+                hit, props = picker(artist, mouseevent)
+
+              to determine the hit test.  if the mouse event is over the
+              artist, return *hit=True* and props is a dictionary of
+              properties you want added to the PickEvent attributes.
+
+        """
+        self._picker = picker
+
+    def get_picker(self):
+        """
+        Return the picking behavior of the artist.
+
+        The possible values are described in `.set_picker`.
+
+        See Also
+        --------
+        set_picker, pickable, pick
+        """
+        return self._picker
+
+    def get_url(self):
+        """Return the url."""
+        return self._url
+
+    def set_url(self, url):
+        """
+        Set the url for the artist.
+
+        Parameters
+        ----------
+        url : str
+        """
+        self._url = url
+
+    def get_gid(self):
+        """Return the group id."""
+        return self._gid
+
+    def set_gid(self, gid):
+        """
+        Set the (group) id for the artist.
+
+        Parameters
+        ----------
+        gid : str
+        """
+        self._gid = gid
+
+    def get_snap(self):
+        """
+        Returns the snap setting.
+
+        See `.set_snap` for details.
+        """
+        if rcParams['path.snap']:
+            return self._snap
+        else:
+            return False
+
+    def set_snap(self, snap):
+        """
+        Set the snapping behavior.
+
+        Snapping aligns positions with the pixel grid, which results in
+        clearer images. For example, if a black line of 1px width was
+        defined at a position in between two pixels, the resulting image
+        would contain the interpolated value of that line in the pixel grid,
+        which would be a grey value on both adjacent pixel positions. In
+        contrast, snapping will move the line to the nearest integer pixel
+        value, so that the resulting image will really contain a 1px wide
+        black line.
+
+        Snapping is currently only supported by the Agg and MacOSX backends.
+
+        Parameters
+        ----------
+        snap : bool or None
+            Possible values:
+
+            - *True*: Snap vertices to the nearest pixel center.
+            - *False*: Do not modify vertex positions.
+            - *None*: (auto) If the path contains only rectilinear line
+              segments, round to the nearest pixel center.
+        """
+        self._snap = snap
+        self.stale = True
+
+    def get_sketch_params(self):
+        """
+        Returns the sketch parameters for the artist.
+
+        Returns
+        -------
+        sketch_params : tuple or None
+
+            A 3-tuple with the following elements:
+
+            - *scale*: The amplitude of the wiggle perpendicular to the
+              source line.
+            - *length*: The length of the wiggle along the line.
+            - *randomness*: The scale factor by which the length is
+              shrunken or expanded.
+
+            Returns *None* if no sketch parameters were set.
+        """
+        return self._sketch
+
+    def set_sketch_params(self, scale=None, length=None, randomness=None):
+        """
+        Sets the sketch parameters.
+
+        Parameters
+        ----------
+        scale : float, optional
+            The amplitude of the wiggle perpendicular to the source
+            line, in pixels.  If scale is `None`, or not provided, no
+            sketch filter will be provided.
+        length : float, optional
+             The length of the wiggle along the line, in pixels
+             (default 128.0)
+        randomness : float, optional
+            The scale factor by which the length is shrunken or
+            expanded (default 16.0)
+
+            .. ACCEPTS: (scale: float, length: float, randomness: float)
+        """
+        if scale is None:
+            self._sketch = None
+        else:
+            self._sketch = (scale, length or 128.0, randomness or 16.0)
+        self.stale = True
+
+    def set_path_effects(self, path_effects):
+        """Set the path effects.
+
+        Parameters
+        ----------
+        path_effects : `.AbstractPathEffect`
+        """
+        self._path_effects = path_effects
+        self.stale = True
+
+    def get_path_effects(self):
+        return self._path_effects
+
+    def get_figure(self):
+        """Return the `.Figure` instance the artist belongs to."""
+        return self.figure
+
+    def set_figure(self, fig):
+        """
+        Set the `.Figure` instance the artist belongs to.
+
+        Parameters
+        ----------
+        fig : `.Figure`
+        """
+        # if this is a no-op just return
+        if self.figure is fig:
+            return
+        # if we currently have a figure (the case of both `self.figure`
+        # and *fig* being none is taken care of above) we then user is
+        # trying to change the figure an artist is associated with which
+        # is not allowed for the same reason as adding the same instance
+        # to more than one Axes
+        if self.figure is not None:
+            raise RuntimeError("Can not put single artist in "
+                               "more than one figure")
+        self.figure = fig
+        if self.figure and self.figure is not self:
+            self.pchanged()
+        self.stale = True
+
+    def set_clip_box(self, clipbox):
+        """
+        Set the artist's clip `.Bbox`.
+
+        Parameters
+        ----------
+        clipbox : `.Bbox`
+        """
+        self.clipbox = clipbox
+        self.pchanged()
+        self.stale = True
+
+    def set_clip_path(self, path, transform=None):
+        """
+        Set the artist's clip path.
+
+        Parameters
+        ----------
+        path : `.Patch` or `.Path` or `.TransformedPath` or None
+            The clip path. If given a `.Path`, *transform* must be provided as
+            well. If *None*, a previously set clip path is removed.
+        transform : `~matplotlib.transforms.Transform`, optional
+            Only used if *path* is a `.Path`, in which case the given `.Path`
+            is converted to a `.TransformedPath` using *transform*.
+
+        Notes
+        -----
+        For efficiency, if *path* is a `.Rectangle` this method will set the
+        clipping box to the corresponding rectangle and set the clipping path
+        to ``None``.
+
+        For technical reasons (support of ``setp``), a tuple
+        (*path*, *transform*) is also accepted as a single positional
+        parameter.
+
+        .. ACCEPTS: Patch or (Path, Transform) or None
+        """
+        from matplotlib.patches import Patch, Rectangle
+
+        success = False
+        if transform is None:
+            if isinstance(path, Rectangle):
+                self.clipbox = TransformedBbox(Bbox.unit(),
+                                               path.get_transform())
+                self._clippath = None
+                success = True
+            elif isinstance(path, Patch):
+                self._clippath = TransformedPatchPath(path)
+                success = True
+            elif isinstance(path, tuple):
+                path, transform = path
+
+        if path is None:
+            self._clippath = None
+            success = True
+        elif isinstance(path, Path):
+            self._clippath = TransformedPath(path, transform)
+            success = True
+        elif isinstance(path, TransformedPatchPath):
+            self._clippath = path
+            success = True
+        elif isinstance(path, TransformedPath):
+            self._clippath = path
+            success = True
+
+        if not success:
+            raise TypeError(
+                "Invalid arguments to set_clip_path, of type {} and {}"
+                .format(type(path).__name__, type(transform).__name__))
+        # This may result in the callbacks being hit twice, but guarantees they
+        # will be hit at least once.
+        self.pchanged()
+        self.stale = True
+
+    def get_alpha(self):
+        """
+        Return the alpha value used for blending - not supported on all
+        backends
+        """
+        return self._alpha
+
+    def get_visible(self):
+        """Return the visibility."""
+        return self._visible
+
+    def get_animated(self):
+        """Return the animated state."""
+        return self._animated
+
+    def get_in_layout(self):
+        """
+        Return boolean flag, ``True`` if artist is included in layout
+        calculations.
+
+        E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
+        `.Figure.tight_layout()`, and
+        ``fig.savefig(fname, bbox_inches='tight')``.
+        """
+        return self._in_layout
+
+    def get_clip_on(self):
+        """Return whether the artist uses clipping."""
+        return self._clipon
+
+    def get_clip_box(self):
+        """Return the clipbox."""
+        return self.clipbox
+
+    def get_clip_path(self):
+        """Return the clip path."""
+        return self._clippath
+
+    def get_transformed_clip_path_and_affine(self):
+        '''
+        Return the clip path with the non-affine part of its
+        transformation applied, and the remaining affine part of its
+        transformation.
+        '''
+        if self._clippath is not None:
+            return self._clippath.get_transformed_path_and_affine()
+        return None, None
+
+    def set_clip_on(self, b):
+        """
+        Set whether the artist uses clipping.
+
+        When False artists will be visible out side of the axes which
+        can lead to unexpected results.
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._clipon = b
+        # This may result in the callbacks being hit twice, but ensures they
+        # are hit at least once
+        self.pchanged()
+        self.stale = True
+
+    def _set_gc_clip(self, gc):
+        'Set the clip properly for the gc'
+        if self._clipon:
+            if self.clipbox is not None:
+                gc.set_clip_rectangle(self.clipbox)
+            gc.set_clip_path(self._clippath)
+        else:
+            gc.set_clip_rectangle(None)
+            gc.set_clip_path(None)
+
+    def get_rasterized(self):
+        """Return whether the artist is to be rasterized."""
+        return self._rasterized
+
+    def set_rasterized(self, rasterized):
+        """
+        Force rasterized (bitmap) drawing in vector backend output.
+
+        Defaults to None, which implies the backend's default behavior.
+
+        Parameters
+        ----------
+        rasterized : bool or None
+        """
+        if rasterized and not hasattr(self.draw, "_supports_rasterization"):
+            cbook._warn_external(
+                "Rasterization of '%s' will be ignored" % self)
+
+        self._rasterized = rasterized
+
+    def get_agg_filter(self):
+        """Return filter function to be used for agg filter."""
+        return self._agg_filter
+
+    def set_agg_filter(self, filter_func):
+        """Set the agg filter.
+
+        Parameters
+        ----------
+        filter_func : callable
+            A filter function, which takes a (m, n, 3) float array and a dpi
+            value, and returns a (m, n, 3) array.
+
+            .. ACCEPTS: a filter function, which takes a (m, n, 3) float array
+                and a dpi value, and returns a (m, n, 3) array
+        """
+        self._agg_filter = filter_func
+        self.stale = True
+
+    def draw(self, renderer, *args, **kwargs):
+        """
+        Draw the Artist using the given renderer.
+
+        This method will be overridden in the Artist subclasses. Typically,
+        it is implemented to not have any effect if the Artist is not visible
+        (`.Artist.get_visible` is *False*).
+
+        Parameters
+        ----------
+        renderer : `.RendererBase` subclass.
+        """
+        if not self.get_visible():
+            return
+        self.stale = False
+
+    def set_alpha(self, alpha):
+        """
+        Set the alpha value used for blending - not supported on all backends.
+
+        Parameters
+        ----------
+        alpha : float or None
+        """
+        if alpha is not None and not isinstance(alpha, Number):
+            raise TypeError('alpha must be a float or None')
+        self._alpha = alpha
+        self.pchanged()
+        self.stale = True
+
+    def set_visible(self, b):
+        """
+        Set the artist's visibility.
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._visible = b
+        self.pchanged()
+        self.stale = True
+
+    def set_animated(self, b):
+        """
+        Set the artist's animation state.
+
+        Parameters
+        ----------
+        b : bool
+        """
+        if self._animated != b:
+            self._animated = b
+            self.pchanged()
+
+    def set_in_layout(self, in_layout):
+        """
+        Set if artist is to be included in layout calculations,
+        E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
+        `.Figure.tight_layout()`, and
+        ``fig.savefig(fname, bbox_inches='tight')``.
+
+        Parameters
+        ----------
+        in_layout : bool
+        """
+        self._in_layout = in_layout
+
+    def update(self, props):
+        """
+        Update this artist's properties from the dictionary *props*.
+        """
+        def _update_property(self, k, v):
+            """Sorting out how to update property (setter or setattr).
+
+            Parameters
+            ----------
+            k : str
+                The name of property to update
+            v : obj
+                The value to assign to the property
+
+            Returns
+            -------
+            ret : obj or None
+                If using a `set_*` method return it's return, else None.
+            """
+            k = k.lower()
+            # white list attributes we want to be able to update through
+            # art.update, art.set, setp
+            if k in {'axes'}:
+                return setattr(self, k, v)
+            else:
+                func = getattr(self, 'set_' + k, None)
+                if not callable(func):
+                    raise AttributeError('{!r} object has no property {!r}'
+                                         .format(type(self).__name__, k))
+                return func(v)
+
+        with cbook._setattr_cm(self, eventson=False):
+            ret = [_update_property(self, k, v) for k, v in props.items()]
+
+        if len(ret):
+            self.pchanged()
+            self.stale = True
+        return ret
+
+    def get_label(self):
+        """Return the label used for this artist in the legend."""
+        return self._label
+
+    def set_label(self, s):
+        """
+        Set a label that will be displayed in the legend.
+
+        Parameters
+        ----------
+        s : object
+            *s* will be converted to a string by calling `str`.
+        """
+        if s is not None:
+            self._label = str(s)
+        else:
+            self._label = None
+        self.pchanged()
+        self.stale = True
+
+    def get_zorder(self):
+        """Return the artist's zorder."""
+        return self.zorder
+
+    def set_zorder(self, level):
+        """
+        Set the zorder for the artist.  Artists with lower zorder
+        values are drawn first.
+
+        Parameters
+        ----------
+        level : float
+        """
+        if level is None:
+            level = self.__class__.zorder
+        self.zorder = level
+        self.pchanged()
+        self.stale = True
+
+    @property
+    def sticky_edges(self):
+        """
+        ``x`` and ``y`` sticky edge lists for autoscaling.
+
+        When performing autoscaling, if a data limit coincides with a value in
+        the corresponding sticky_edges list, then no margin will be added--the
+        view limit "sticks" to the edge. A typical use case is histograms,
+        where one usually expects no margin on the bottom edge (0) of the
+        histogram.
+
+        This attribute cannot be assigned to; however, the ``x`` and ``y``
+        lists can be modified in place as needed.
+
+        Examples
+        --------
+        >>> artist.sticky_edges.x[:] = (xmin, xmax)
+        >>> artist.sticky_edges.y[:] = (ymin, ymax)
+
+        """
+        return self._sticky_edges
+
+    def update_from(self, other):
+        'Copy properties from *other* to *self*.'
+        self._transform = other._transform
+        self._transformSet = other._transformSet
+        self._visible = other._visible
+        self._alpha = other._alpha
+        self.clipbox = other.clipbox
+        self._clipon = other._clipon
+        self._clippath = other._clippath
+        self._label = other._label
+        self._sketch = other._sketch
+        self._path_effects = other._path_effects
+        self.sticky_edges.x[:] = other.sticky_edges.x[:]
+        self.sticky_edges.y[:] = other.sticky_edges.y[:]
+        self.pchanged()
+        self.stale = True
+
+    def properties(self):
+        """Return a dictionary of all the properties of the artist."""
+        return ArtistInspector(self).properties()
+
+    def set(self, **kwargs):
+        """A property batch setter.  Pass *kwargs* to set properties."""
+        kwargs = cbook.normalize_kwargs(kwargs, self)
+        props = OrderedDict(
+            sorted(kwargs.items(), reverse=True,
+                   key=lambda x: (self._prop_order.get(x[0], 0), x[0])))
+        return self.update(props)
+
+    def findobj(self, match=None, include_self=True):
+        """
+        Find artist objects.
+
+        Recursively find all `.Artist` instances contained in the artist.
+
+        Parameters
+        ----------
+        match
+            A filter criterion for the matches. This can be
+
+            - *None*: Return all objects contained in artist.
+            - A function with signature ``def match(artist: Artist) -> bool``.
+              The result will only contain artists for which the function
+              returns *True*.
+            - A class instance: e.g., `.Line2D`. The result will only contain
+              artists of this class or its subclasses (``isinstance`` check).
+
+        include_self : bool
+            Include *self* in the list to be checked for a match.
+
+        Returns
+        -------
+        artists : list of `.Artist`
+
+        """
+        if match is None:  # always return True
+            def matchfunc(x):
+                return True
+        elif isinstance(match, type) and issubclass(match, Artist):
+            def matchfunc(x):
+                return isinstance(x, match)
+        elif callable(match):
+            matchfunc = match
+        else:
+            raise ValueError('match must be None, a matplotlib.artist.Artist '
+                             'subclass, or a callable')
+
+        artists = sum([c.findobj(matchfunc) for c in self.get_children()], [])
+        if include_self and matchfunc(self):
+            artists.append(self)
+        return artists
+
+    def get_cursor_data(self, event):
+        """
+        Return the cursor data for a given event.
+
+        .. note::
+            This method is intended to be overridden by artist subclasses.
+            As an end-user of Matplotlib you will most likely not call this
+            method yourself.
+
+        Cursor data can be used by Artists to provide additional context
+        information for a given event. The default implementation just returns
+        *None*.
+
+        Subclasses can override the method and return arbitrary data. However,
+        when doing so, they must ensure that `.format_cursor_data` can convert
+        the data to a string representation.
+
+        The only current use case is displaying the z-value of an `.AxesImage`
+        in the status bar of a plot window, while moving the mouse.
+
+        Parameters
+        ----------
+        event : `matplotlib.backend_bases.MouseEvent`
+
+        See Also
+        --------
+        format_cursor_data
+
+        """
+        return None
+
+    def format_cursor_data(self, data):
+        """
+        Return a string representation of *data*.
+
+        .. note::
+            This method is intended to be overridden by artist subclasses.
+            As an end-user of Matplotlib you will most likely not call this
+            method yourself.
+
+        The default implementation converts ints and floats and arrays of ints
+        and floats into a comma-separated string enclosed in square brackets.
+
+        See Also
+        --------
+        get_cursor_data
+        """
+        try:
+            data[0]
+        except (TypeError, IndexError):
+            data = [data]
+        data_str = ', '.join('{:0.3g}'.format(item) for item in data
+                             if isinstance(item, Number))
+        return "[" + data_str + "]"
+
+    @property
+    def mouseover(self):
+        """
+        If this property is set to *True*, the artist will be queried for
+        custom context information when the mouse cursor moves over it.
+
+        See also :meth:`get_cursor_data`, :class:`.ToolCursorPosition` and
+        :class:`.NavigationToolbar2`.
+        """
+        return self._mouseover
+
+    @mouseover.setter
+    def mouseover(self, val):
+        val = bool(val)
+        self._mouseover = val
+        ax = self.axes
+        if ax:
+            if val:
+                ax._mouseover_set.add(self)
+            else:
+                ax._mouseover_set.discard(self)
+
+
+class ArtistInspector:
+    """
+    A helper class to inspect an `~matplotlib.artist.Artist` and return
+    information about its settable properties and their current values.
+    """
+
+    def __init__(self, o):
+        r"""
+        Initialize the artist inspector with an `Artist` or an iterable of
+        `Artist`\s.  If an iterable is used, we assume it is a homogeneous
+        sequence (all `Artists` are of the same type) and it is your
+        responsibility to make sure this is so.
+        """
+        if not isinstance(o, Artist):
+            if np.iterable(o):
+                o = list(o)
+                if len(o):
+                    o = o[0]
+
+        self.oorig = o
+        if not isinstance(o, type):
+            o = type(o)
+        self.o = o
+
+        self.aliasd = self.get_aliases()
+
+    def get_aliases(self):
+        """
+        Get a dict mapping property fullnames to sets of aliases for each alias
+        in the :class:`~matplotlib.artist.ArtistInspector`.
+
+        e.g., for lines::
+
+          {'markerfacecolor': {'mfc'},
+           'linewidth'      : {'lw'},
+          }
+        """
+        names = [name for name in dir(self.o)
+                 if name.startswith(('set_', 'get_'))
+                    and callable(getattr(self.o, name))]
+        aliases = {}
+        for name in names:
+            func = getattr(self.o, name)
+            if not self.is_alias(func):
+                continue
+            propname = re.search("`({}.*)`".format(name[:4]),  # get_.*/set_.*
+                                 inspect.getdoc(func)).group(1)
+            aliases.setdefault(propname[4:], set()).add(name[4:])
+        return aliases
+
+    _get_valid_values_regex = re.compile(
+        r"\n\s*(?:\.\.\s+)?ACCEPTS:\s*((?:.|\n)*?)(?:$|(?:\n\n))"
+    )
+
+    def get_valid_values(self, attr):
+        """
+        Get the legal arguments for the setter associated with *attr*.
+
+        This is done by querying the docstring of the setter for a line that
+        begins with "ACCEPTS:" or ".. ACCEPTS:", and then by looking for a
+        numpydoc-style documentation for the setter's first argument.
+        """
+
+        name = 'set_%s' % attr
+        if not hasattr(self.o, name):
+            raise AttributeError('%s has no function %s' % (self.o, name))
+        func = getattr(self.o, name)
+
+        docstring = inspect.getdoc(func)
+        if docstring is None:
+            return 'unknown'
+
+        if docstring.startswith('Alias for '):
+            return None
+
+        match = self._get_valid_values_regex.search(docstring)
+        if match is not None:
+            return re.sub("\n *", " ", match.group(1))
+
+        # Much faster than list(inspect.signature(func).parameters)[1],
+        # although barely relevant wrt. matplotlib's total import time.
+        param_name = func.__code__.co_varnames[1]
+        # We could set the presence * based on whether the parameter is a
+        # varargs (it can't be a varkwargs) but it's not really worth the it.
+        match = re.search(r"(?m)^ *\*?{} : (.+)".format(param_name), docstring)
+        if match:
+            return match.group(1)
+
+        return 'unknown'
+
+    def _get_setters_and_targets(self):
+        """
+        Get the attribute strings and a full path to where the setter
+        is defined for all setters in an object.
+        """
+        setters = []
+        for name in dir(self.o):
+            if not name.startswith('set_'):
+                continue
+            func = getattr(self.o, name)
+            if (not callable(func)
+                    or len(inspect.signature(func).parameters) < 2
+                    or self.is_alias(func)):
+                continue
+            setters.append(
+                (name[4:], f"{func.__module__}.{func.__qualname__}"))
+        return setters
+
+    def _replace_path(self, source_class):
+        """
+        Changes the full path to the public API path that is used
+        in sphinx. This is needed for links to work.
+        """
+        replace_dict = {'_base._AxesBase': 'Axes',
+                        '_axes.Axes': 'Axes'}
+        for key, value in replace_dict.items():
+            source_class = source_class.replace(key, value)
+        return source_class
+
+    def get_setters(self):
+        """
+        Get the attribute strings with setters for object.  e.g., for a line,
+        return ``['markerfacecolor', 'linewidth', ....]``.
+        """
+        return [prop for prop, target in self._get_setters_and_targets()]
+
+    def is_alias(self, o):
+        """Return whether method object *o* is an alias for another method."""
+        ds = inspect.getdoc(o)
+        if ds is None:
+            return False
+        return ds.startswith('Alias for ')
+
+    def aliased_name(self, s):
+        """
+        Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME'.
+
+        e.g., for the line markerfacecolor property, which has an
+        alias, return 'markerfacecolor or mfc' and for the transform
+        property, which does not, return 'transform'.
+        """
+        aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
+        return s + aliases
+
+    def aliased_name_rest(self, s, target):
+        """
+        Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME',
+        formatted for ReST.
+
+        e.g., for the line markerfacecolor property, which has an
+        alias, return 'markerfacecolor or mfc' and for the transform
+        property, which does not, return 'transform'.
+        """
+        aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
+        return ':meth:`%s <%s>`%s' % (s, target, aliases)
+
+    def pprint_setters(self, prop=None, leadingspace=2):
+        """
+        If *prop* is *None*, return a list of strings of all settable
+        properties and their valid values.
+
+        If *prop* is not *None*, it is a valid property name and that
+        property will be returned as a string of property : valid
+        values.
+        """
+        if leadingspace:
+            pad = ' ' * leadingspace
+        else:
+            pad = ''
+        if prop is not None:
+            accepts = self.get_valid_values(prop)
+            return '%s%s: %s' % (pad, prop, accepts)
+
+        attrs = self._get_setters_and_targets()
+        attrs.sort()
+        lines = []
+
+        for prop, path in attrs:
+            accepts = self.get_valid_values(prop)
+            name = self.aliased_name(prop)
+
+            lines.append('%s%s: %s' % (pad, name, accepts))
+        return lines
+
+    def pprint_setters_rest(self, prop=None, leadingspace=4):
+        """
+        If *prop* is *None*, return a list of strings of all settable
+        properties and their valid values.  Format the output for ReST
+
+        If *prop* is not *None*, it is a valid property name and that
+        property will be returned as a string of property : valid
+        values.
+        """
+        if leadingspace:
+            pad = ' ' * leadingspace
+        else:
+            pad = ''
+        if prop is not None:
+            accepts = self.get_valid_values(prop)
+            return '%s%s: %s' % (pad, prop, accepts)
+
+        attrs = sorted(self._get_setters_and_targets())
+
+        names = [self.aliased_name_rest(prop, target).replace(
+            '_base._AxesBase', 'Axes').replace(
+            '_axes.Axes', 'Axes')
+                 for prop, target in attrs]
+        accepts = [self.get_valid_values(prop) for prop, target in attrs]
+
+        col0_len = max(len(n) for n in names)
+        col1_len = max(len(a) for a in accepts)
+        table_formatstr = pad + '   ' + '=' * col0_len + '   ' + '=' * col1_len
+
+        return [
+            '',
+            pad + '.. table::',
+            pad + '   :class: property-table',
+            '',
+            table_formatstr,
+            pad + '   ' + 'Property'.ljust(col0_len)
+            + '   ' + 'Description'.ljust(col1_len),
+            table_formatstr,
+            *[pad + '   ' + n.ljust(col0_len) + '   ' + a.ljust(col1_len)
+              for n, a in zip(names, accepts)],
+            table_formatstr,
+            '',
+        ]
+
+    def properties(self):
+        """Return a dictionary mapping property name -> value."""
+        o = self.oorig
+        getters = [name for name in dir(o)
+                   if name.startswith('get_') and callable(getattr(o, name))]
+        getters.sort()
+        d = {}
+        for name in getters:
+            func = getattr(o, name)
+            if self.is_alias(func):
+                continue
+            try:
+                with warnings.catch_warnings():
+                    warnings.simplefilter('ignore')
+                    val = func()
+            except Exception:
+                continue
+            else:
+                d[name[4:]] = val
+        return d
+
+    def pprint_getters(self):
+        """Return the getters and actual values as list of strings."""
+        lines = []
+        for name, val in sorted(self.properties().items()):
+            if getattr(val, 'shape', ()) != () and len(val) > 6:
+                s = str(val[:6]) + '...'
+            else:
+                s = str(val)
+            s = s.replace('\n', ' ')
+            if len(s) > 50:
+                s = s[:50] + '...'
+            name = self.aliased_name(name)
+            lines.append('    %s = %s' % (name, s))
+        return lines
+
+
+def getp(obj, property=None):
+    """
+    Return the value of object's property.  *property* is an optional string
+    for the property you want to return
+
+    Example usage::
+
+        getp(obj)  # get all the object properties
+        getp(obj, 'linestyle')  # get the linestyle property
+
+    *obj* is a :class:`Artist` instance, e.g.,
+    :class:`~matplotlib.lines.Line2D` or an instance of a
+    :class:`~matplotlib.axes.Axes` or :class:`matplotlib.text.Text`.
+    If the *property* is 'somename', this function returns
+
+      obj.get_somename()
+
+    :func:`getp` can be used to query all the gettable properties with
+    ``getp(obj)``. Many properties have aliases for shorter typing, e.g.
+    'lw' is an alias for 'linewidth'.  In the output, aliases and full
+    property names will be listed as:
+
+      property or alias = value
+
+    e.g.:
+
+      linewidth or lw = 2
+    """
+    if property is None:
+        insp = ArtistInspector(obj)
+        ret = insp.pprint_getters()
+        print('\n'.join(ret))
+        return
+
+    func = getattr(obj, 'get_' + property)
+    return func()
+
+# alias
+get = getp
+
+
+def setp(obj, *args, **kwargs):
+    """
+    Set a property on an artist object.
+
+    matplotlib supports the use of :func:`setp` ("set property") and
+    :func:`getp` to set and get object properties, as well as to do
+    introspection on the object.  For example, to set the linestyle of a
+    line to be dashed, you can do::
+
+      >>> line, = plot([1, 2, 3])
+      >>> setp(line, linestyle='--')
+
+    If you want to know the valid types of arguments, you can provide
+    the name of the property you want to set without a value::
+
+      >>> setp(line, 'linestyle')
+          linestyle: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
+
+    If you want to see all the properties that can be set, and their
+    possible values, you can do::
+
+      >>> setp(line)
+          ... long output listing omitted
+
+    You may specify another output file to `setp` if `sys.stdout` is not
+    acceptable for some reason using the *file* keyword-only argument::
+
+      >>> with fopen('output.log') as f:
+      >>>     setp(line, file=f)
+
+    :func:`setp` operates on a single instance or a iterable of
+    instances. If you are in query mode introspecting the possible
+    values, only the first instance in the sequence is used. When
+    actually setting values, all the instances will be set.  e.g.,
+    suppose you have a list of two lines, the following will make both
+    lines thicker and red::
+
+      >>> x = arange(0, 1, 0.01)
+      >>> y1 = sin(2*pi*x)
+      >>> y2 = sin(4*pi*x)
+      >>> lines = plot(x, y1, x, y2)
+      >>> setp(lines, linewidth=2, color='r')
+
+    :func:`setp` works with the MATLAB style string/value pairs or
+    with python kwargs.  For example, the following are equivalent::
+
+      >>> setp(lines, 'linewidth', 2, 'color', 'r')  # MATLAB style
+      >>> setp(lines, linewidth=2, color='r')        # python style
+    """
+
+    if isinstance(obj, Artist):
+        objs = [obj]
+    else:
+        objs = list(cbook.flatten(obj))
+
+    if not objs:
+        return
+
+    insp = ArtistInspector(objs[0])
+
+    # file has to be popped before checking if kwargs is empty
+    printArgs = {}
+    if 'file' in kwargs:
+        printArgs['file'] = kwargs.pop('file')
+
+    if not kwargs and len(args) < 2:
+        if args:
+            print(insp.pprint_setters(prop=args[0]), **printArgs)
+        else:
+            print('\n'.join(insp.pprint_setters()), **printArgs)
+        return
+
+    if len(args) % 2:
+        raise ValueError('The set args must be string, value pairs')
+
+    # put args into ordereddict to maintain order
+    funcvals = OrderedDict((k, v) for k, v in zip(args[::2], args[1::2]))
+    ret = [o.update(funcvals) for o in objs] + [o.set(**kwargs) for o in objs]
+    return list(cbook.flatten(ret))
+
+
+def kwdoc(artist):
+    r"""
+    Inspect an `~matplotlib.artist.Artist` class (using `.ArtistInspector`) and
+    return information about its settable properties and their current values.
+
+    Parameters
+    ----------
+    artist : `~matplotlib.artist.Artist` or an iterable of `Artist`\s
+
+    Returns
+    -------
+    string
+        The settable properties of *artist*, as plain text if
+        :rc:`docstring.hardcopy` is False and as a rst table (intended for
+        use in Sphinx) if it is True.
+    """
+    ai = ArtistInspector(artist)
+    return ('\n'.join(ai.pprint_setters_rest(leadingspace=4))
+            if matplotlib.rcParams['docstring.hardcopy'] else
+            'Properties:\n' + '\n'.join(ai.pprint_setters(leadingspace=4)))
+
+
+docstring.interpd.update(Artist=kwdoc(Artist))

+ 2 - 0
venv/lib/python3.8/site-packages/matplotlib/axes/__init__.py

@@ -0,0 +1,2 @@
+from ._subplots import *
+from ._axes import *

+ 8098 - 0
venv/lib/python3.8/site-packages/matplotlib/axes/_axes.py

@@ -0,0 +1,8098 @@
+import collections.abc
+import functools
+import itertools
+import logging
+import math
+from numbers import Number
+
+import numpy as np
+from numpy import ma
+
+import matplotlib.category as _  # <-registers a category unit converter
+import matplotlib.cbook as cbook
+import matplotlib.collections as mcoll
+import matplotlib.colors as mcolors
+import matplotlib.contour as mcontour
+import matplotlib.dates as _  # <-registers a date unit converter
+import matplotlib.docstring as docstring
+import matplotlib.image as mimage
+import matplotlib.legend as mlegend
+import matplotlib.lines as mlines
+import matplotlib.markers as mmarkers
+import matplotlib.mlab as mlab
+import matplotlib.patches as mpatches
+import matplotlib.path as mpath
+import matplotlib.quiver as mquiver
+import matplotlib.stackplot as mstack
+import matplotlib.streamplot as mstream
+import matplotlib.table as mtable
+import matplotlib.text as mtext
+import matplotlib.ticker as mticker
+import matplotlib.transforms as mtransforms
+import matplotlib.tri as mtri
+from matplotlib import _preprocess_data, rcParams
+from matplotlib.axes._base import _AxesBase, _process_plot_format
+from matplotlib.axes._secondary_axes import SecondaryAxis
+from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer
+
+try:
+    from numpy.lib.histograms import (
+        histogram_bin_edges as _histogram_bin_edges)
+except ImportError:
+    # this function is new in np 1.15
+    def _histogram_bin_edges(arr, bins, range=None, weights=None):
+        # this in True for 1D arrays, and False for None and str
+        if np.ndim(bins) == 1:
+            return bins
+
+        if isinstance(bins, str):
+            # rather than backporting the internals, just do the full
+            # computation.  If this is too slow for users, they can
+            # update numpy, or pick a manual number of bins
+            return np.histogram(arr, bins, range, weights)[1]
+        else:
+            if bins is None:
+                # hard-code numpy's default
+                bins = 10
+            if range is None:
+                range = np.min(arr), np.max(arr)
+
+            return np.linspace(*range, bins + 1)
+
+
+_log = logging.getLogger(__name__)
+
+
+def _make_inset_locator(bounds, trans, parent):
+    """
+    Helper function to locate inset axes, used in
+    `.Axes.inset_axes`.
+
+    A locator gets used in `Axes.set_aspect` to override the default
+    locations...  It is a function that takes an axes object and
+    a renderer and tells `set_aspect` where it is to be placed.
+
+    Here *rect* is a rectangle [l, b, w, h] that specifies the
+    location for the axes in the transform given by *trans* on the
+    *parent*.
+    """
+    _bounds = mtransforms.Bbox.from_bounds(*bounds)
+    _trans = trans
+    _parent = parent
+
+    def inset_locator(ax, renderer):
+        bbox = _bounds
+        bb = mtransforms.TransformedBbox(bbox, _trans)
+        tr = _parent.figure.transFigure.inverted()
+        bb = mtransforms.TransformedBbox(bb, tr)
+        return bb
+
+    return inset_locator
+
+
+# The axes module contains all the wrappers to plotting functions.
+# All the other methods should go in the _AxesBase class.
+
+
+class Axes(_AxesBase):
+    """
+    The `Axes` contains most of the figure elements: `~.axis.Axis`,
+    `~.axis.Tick`, `~.lines.Line2D`, `~.text.Text`, `~.patches.Polygon`, etc.,
+    and sets the coordinate system.
+
+    The `Axes` instance supports callbacks through a callbacks attribute which
+    is a `~.cbook.CallbackRegistry` instance.  The events you can connect to
+    are 'xlim_changed' and 'ylim_changed' and the callback will be called with
+    func(*ax*) where *ax* is the `Axes` instance.
+
+    Attributes
+    ----------
+    dataLim : `.Bbox`
+        The bounding box enclosing all data displayed in the Axes.
+    viewLim : `.Bbox`
+        The view limits in data coordinates.
+
+    """
+    ### Labelling, legend and texts
+
+    @cbook.deprecated("3.1")
+    @property
+    def aname(self):
+        return 'Axes'
+
+    def get_title(self, loc="center"):
+        """
+        Get an axes title.
+
+        Get one of the three available axes titles. The available titles
+        are positioned above the axes in the center, flush with the left
+        edge, and flush with the right edge.
+
+        Parameters
+        ----------
+        loc : {'center', 'left', 'right'}, str, optional
+            Which title to get, defaults to 'center'.
+
+        Returns
+        -------
+        title : str
+            The title text string.
+
+        """
+        titles = {'left': self._left_title,
+                  'center': self.title,
+                  'right': self._right_title}
+        title = cbook._check_getitem(titles, loc=loc.lower())
+        return title.get_text()
+
+    def set_title(self, label, fontdict=None, loc=None, pad=None,
+                    **kwargs):
+        """
+        Set a title for the axes.
+
+        Set one of the three available axes titles. The available titles
+        are positioned above the axes in the center, flush with the left
+        edge, and flush with the right edge.
+
+        Parameters
+        ----------
+        label : str
+            Text to use for the title
+
+        fontdict : dict
+            A dictionary controlling the appearance of the title text,
+            the default *fontdict* is::
+
+               {'fontsize': rcParams['axes.titlesize'],
+                'fontweight' : rcParams['axes.titleweight'],
+                'color' : rcParams['axes.titlecolor'],
+                'verticalalignment': 'baseline',
+                'horizontalalignment': loc}
+
+        loc : {'center', 'left', 'right'}, str, optional
+            Which title to set.
+            If *None*, defaults to :rc:`axes.titlelocation`.
+
+        pad : float
+            The offset of the title from the top of the axes, in points.
+            If *None*, defaults to :rc:`axes.titlepad`.
+
+        Returns
+        -------
+        text : :class:`~matplotlib.text.Text`
+            The matplotlib text instance representing the title
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.text.Text` properties
+            Other keyword arguments are text properties, see
+            :class:`~matplotlib.text.Text` for a list of valid text
+            properties.
+        """
+        if loc is None:
+            loc = rcParams['axes.titlelocation']
+
+        titles = {'left': self._left_title,
+                  'center': self.title,
+                  'right': self._right_title}
+        title = cbook._check_getitem(titles, loc=loc.lower())
+        default = {
+            'fontsize': rcParams['axes.titlesize'],
+            'fontweight': rcParams['axes.titleweight'],
+            'verticalalignment': 'baseline',
+            'horizontalalignment': loc.lower()}
+        titlecolor = rcParams['axes.titlecolor']
+        if not cbook._str_lower_equal(titlecolor, 'auto'):
+            default["color"] = titlecolor
+        if pad is None:
+            pad = rcParams['axes.titlepad']
+        self._set_title_offset_trans(float(pad))
+        title.set_text(label)
+        title.update(default)
+        if fontdict is not None:
+            title.update(fontdict)
+        title.update(kwargs)
+        return title
+
+    def get_xlabel(self):
+        """
+        Get the xlabel text string.
+        """
+        label = self.xaxis.get_label()
+        return label.get_text()
+
+    def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs):
+        """
+        Set the label for the x-axis.
+
+        Parameters
+        ----------
+        xlabel : str
+            The label text.
+
+        labelpad : scalar, optional, default: None
+            Spacing in points from the axes bounding box including ticks
+            and tick labels.
+
+        Other Parameters
+        ----------------
+        **kwargs : `.Text` properties
+            `.Text` properties control the appearance of the label.
+
+        See also
+        --------
+        text : for information on how override and the optional args work
+        """
+        if labelpad is not None:
+            self.xaxis.labelpad = labelpad
+        return self.xaxis.set_label_text(xlabel, fontdict, **kwargs)
+
+    def get_ylabel(self):
+        """
+        Get the ylabel text string.
+        """
+        label = self.yaxis.get_label()
+        return label.get_text()
+
+    def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs):
+        """
+        Set the label for the y-axis.
+
+        Parameters
+        ----------
+        ylabel : str
+            The label text.
+
+        labelpad : scalar, optional, default: None
+            Spacing in points from the axes bounding box including ticks
+            and tick labels.
+
+        Other Parameters
+        ----------------
+        **kwargs : `.Text` properties
+            `.Text` properties control the appearance of the label.
+
+        See also
+        --------
+        text : for information on how override and the optional args work
+
+        """
+        if labelpad is not None:
+            self.yaxis.labelpad = labelpad
+        return self.yaxis.set_label_text(ylabel, fontdict, **kwargs)
+
+    def get_legend_handles_labels(self, legend_handler_map=None):
+        """
+        Return handles and labels for legend
+
+        ``ax.legend()`` is equivalent to ::
+
+          h, l = ax.get_legend_handles_labels()
+          ax.legend(h, l)
+
+        """
+
+        # pass through to legend.
+        handles, labels = mlegend._get_legend_handles_labels([self],
+                legend_handler_map)
+        return handles, labels
+
+    @docstring.dedent_interpd
+    def legend(self, *args, **kwargs):
+        """
+        Place a legend on the axes.
+
+        Call signatures::
+
+            legend()
+            legend(labels)
+            legend(handles, labels)
+
+        The call signatures correspond to three different ways how to use
+        this method.
+
+        **1. Automatic detection of elements to be shown in the legend**
+
+        The elements to be added to the legend are automatically determined,
+        when you do not pass in any extra arguments.
+
+        In this case, the labels are taken from the artist. You can specify
+        them either at artist creation or by calling the
+        :meth:`~.Artist.set_label` method on the artist::
+
+            line, = ax.plot([1, 2, 3], label='Inline label')
+            ax.legend()
+
+        or::
+
+            line, = ax.plot([1, 2, 3])
+            line.set_label('Label via method')
+            ax.legend()
+
+        Specific lines can be excluded from the automatic legend element
+        selection by defining a label starting with an underscore.
+        This is default for all artists, so calling `Axes.legend` without
+        any arguments and without setting the labels manually will result in
+        no legend being drawn.
+
+
+        **2. Labeling existing plot elements**
+
+        To make a legend for lines which already exist on the axes
+        (via plot for instance), simply call this function with an iterable
+        of strings, one for each legend item. For example::
+
+            ax.plot([1, 2, 3])
+            ax.legend(['A simple line'])
+
+        Note: This way of using is discouraged, because the relation between
+        plot elements and labels is only implicit by their order and can
+        easily be mixed up.
+
+
+        **3. Explicitly defining the elements in the legend**
+
+        For full control of which artists have a legend entry, it is possible
+        to pass an iterable of legend artists followed by an iterable of
+        legend labels respectively::
+
+            legend((line1, line2, line3), ('label1', 'label2', 'label3'))
+
+        Parameters
+        ----------
+        handles : sequence of `.Artist`, optional
+            A list of Artists (lines, patches) to be added to the legend.
+            Use this together with *labels*, if you need full control on what
+            is shown in the legend and the automatic mechanism described above
+            is not sufficient.
+
+            The length of handles and labels should be the same in this
+            case. If they are not, they are truncated to the smaller length.
+
+        labels : list of str, optional
+            A list of labels to show next to the artists.
+            Use this together with *handles*, if you need full control on what
+            is shown in the legend and the automatic mechanism described above
+            is not sufficient.
+
+        Other Parameters
+        ----------------
+        %(_legend_kw_doc)s
+
+        Returns
+        -------
+        legend : `~matplotlib.legend.Legend`
+
+        Notes
+        -----
+        Not all kinds of artist are supported by the legend command. See
+        :doc:`/tutorials/intermediate/legend_guide` for details.
+
+        Examples
+        --------
+        .. plot:: gallery/text_labels_and_annotations/legend.py
+        """
+        handles, labels, extra_args, kwargs = mlegend._parse_legend_args(
+                [self],
+                *args,
+                **kwargs)
+        if len(extra_args):
+            raise TypeError('legend only accepts two non-keyword arguments')
+        self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
+        self.legend_._remove_method = self._remove_legend
+        return self.legend_
+
+    def _remove_legend(self, legend):
+        self.legend_ = None
+
+    def inset_axes(self, bounds, *, transform=None, zorder=5,
+            **kwargs):
+        """
+        Add a child inset axes to this existing axes.
+
+        Warnings
+        --------
+        This method is experimental as of 3.0, and the API may change.
+
+        Parameters
+        ----------
+        bounds : [x0, y0, width, height]
+            Lower-left corner of inset axes, and its width and height.
+
+        transform : `.Transform`
+            Defaults to `ax.transAxes`, i.e. the units of *rect* are in
+            axes-relative coordinates.
+
+        zorder : number
+            Defaults to 5 (same as `.Axes.legend`).  Adjust higher or lower
+            to change whether it is above or below data plotted on the
+            parent axes.
+
+        **kwargs
+            Other keyword arguments are passed on to the `.Axes` child axes.
+
+        Returns
+        -------
+        ax
+            The created `~.axes.Axes` instance.
+
+        Examples
+        --------
+        This example makes two inset axes, the first is in axes-relative
+        coordinates, and the second in data-coordinates::
+
+            fig, ax = plt.subplots()
+            ax.plot(range(10))
+            axin1 = ax.inset_axes([0.8, 0.1, 0.15, 0.15])
+            axin2 = ax.inset_axes(
+                    [5, 7, 2.3, 2.3], transform=ax.transData)
+
+        """
+        if transform is None:
+            transform = self.transAxes
+        label = kwargs.pop('label', 'inset_axes')
+
+        # This puts the rectangle into figure-relative coordinates.
+        inset_locator = _make_inset_locator(bounds, transform, self)
+        bb = inset_locator(None, None)
+
+        inset_ax = Axes(self.figure, bb.bounds, zorder=zorder,
+                label=label, **kwargs)
+
+        # this locator lets the axes move if in data coordinates.
+        # it gets called in `ax.apply_aspect() (of all places)
+        inset_ax.set_axes_locator(inset_locator)
+
+        self.add_child_axes(inset_ax)
+
+        return inset_ax
+
+    def indicate_inset(self, bounds, inset_ax=None, *, transform=None,
+            facecolor='none', edgecolor='0.5', alpha=0.5,
+            zorder=4.99, **kwargs):
+        """
+        Add an inset indicator to the axes.  This is a rectangle on the plot
+        at the position indicated by *bounds* that optionally has lines that
+        connect the rectangle to an inset axes (`.Axes.inset_axes`).
+
+        Warnings
+        --------
+        This method is experimental as of 3.0, and the API may change.
+
+
+        Parameters
+        ----------
+        bounds : [x0, y0, width, height]
+            Lower-left corner of rectangle to be marked, and its width
+            and height.
+
+        inset_ax : `.Axes`
+            An optional inset axes to draw connecting lines to.  Two lines are
+            drawn connecting the indicator box to the inset axes on corners
+            chosen so as to not overlap with the indicator box.
+
+        transform : `.Transform`
+            Transform for the rectangle co-ordinates. Defaults to
+            `ax.transAxes`, i.e. the units of *rect* are in axes-relative
+            coordinates.
+
+        facecolor : Matplotlib color
+            Facecolor of the rectangle (default 'none').
+
+        edgecolor : Matplotlib color
+            Color of the rectangle and color of the connecting lines.  Default
+            is '0.5'.
+
+        alpha : float
+            Transparency of the rectangle and connector lines.  Default is 0.5.
+
+        zorder : float
+            Drawing order of the rectangle and connector lines. Default is 4.99
+            (just below the default level of inset axes).
+
+        **kwargs
+            Other keyword arguments are passed on to the rectangle patch.
+
+        Returns
+        -------
+        rectangle_patch : `.patches.Rectangle`
+             The indicator frame.
+
+        connector_lines : 4-tuple of `.patches.ConnectionPatch`
+            The four connector lines connecting to (lower_left, upper_left,
+            lower_right upper_right) corners of *inset_ax*. Two lines are
+            set with visibility to *False*,  but the user can set the
+            visibility to True if the automatic choice is not deemed correct.
+
+        """
+        # to make the axes connectors work, we need to apply the aspect to
+        # the parent axes.
+        self.apply_aspect()
+
+        if transform is None:
+            transform = self.transData
+        label = kwargs.pop('label', 'indicate_inset')
+
+        x, y, width, height = bounds
+        rectangle_patch = mpatches.Rectangle(
+            (x, y), width, height,
+            facecolor=facecolor, edgecolor=edgecolor, alpha=alpha,
+            zorder=zorder,  label=label, transform=transform, **kwargs)
+        self.add_patch(rectangle_patch)
+
+        connects = []
+
+        if inset_ax is not None:
+            # connect the inset_axes to the rectangle
+            for xy_inset_ax in [(0, 0), (0, 1), (1, 0), (1, 1)]:
+                # inset_ax positions are in axes coordinates
+                # The 0, 1 values define the four edges if the inset_ax
+                # lower_left, upper_left, lower_right upper_right.
+                ex, ey = xy_inset_ax
+                if self.xaxis.get_inverted():
+                    ex = 1 - ex
+                if self.yaxis.get_inverted():
+                    ey = 1 - ey
+                xy_data = x + ex * width, y + ey * height
+                p = mpatches.ConnectionPatch(
+                    xyA=xy_inset_ax, coordsA=inset_ax.transAxes,
+                    xyB=xy_data, coordsB=self.transData,
+                    arrowstyle="-", zorder=zorder,
+                    edgecolor=edgecolor, alpha=alpha)
+                connects.append(p)
+                self.add_patch(p)
+
+            # decide which two of the lines to keep visible....
+            pos = inset_ax.get_position()
+            bboxins = pos.transformed(self.figure.transFigure)
+            rectbbox = mtransforms.Bbox.from_bounds(
+                *bounds
+            ).transformed(transform)
+            x0 = rectbbox.x0 < bboxins.x0
+            x1 = rectbbox.x1 < bboxins.x1
+            y0 = rectbbox.y0 < bboxins.y0
+            y1 = rectbbox.y1 < bboxins.y1
+            connects[0].set_visible(x0 ^ y0)
+            connects[1].set_visible(x0 == y1)
+            connects[2].set_visible(x1 == y0)
+            connects[3].set_visible(x1 ^ y1)
+
+        return rectangle_patch, tuple(connects) if connects else None
+
+    def indicate_inset_zoom(self, inset_ax, **kwargs):
+        """
+        Add an inset indicator rectangle to the axes based on the axis
+        limits for an *inset_ax* and draw connectors between *inset_ax*
+        and the rectangle.
+
+        Warnings
+        --------
+        This method is experimental as of 3.0, and the API may change.
+
+        Parameters
+        ----------
+        inset_ax : `.Axes`
+            Inset axes to draw connecting lines to.  Two lines are
+            drawn connecting the indicator box to the inset axes on corners
+            chosen so as to not overlap with the indicator box.
+
+        **kwargs
+            Other keyword arguments are passed on to `.Axes.indicate_inset`
+
+        Returns
+        -------
+        rectangle_patch : `.Patches.Rectangle`
+             Rectangle artist.
+
+        connector_lines : 4-tuple of `.Patches.ConnectionPatch`
+            Each of four connector lines coming from the rectangle drawn on
+            this axis, in the order lower left, upper left, lower right,
+            upper right.
+            Two are set with visibility to *False*,  but the user can
+            set the visibility to *True* if the automatic choice is not deemed
+            correct.
+        """
+
+        xlim = inset_ax.get_xlim()
+        ylim = inset_ax.get_ylim()
+        rect = (xlim[0], ylim[0], xlim[1] - xlim[0], ylim[1] - ylim[0])
+        return self.indicate_inset(rect, inset_ax, **kwargs)
+
+    @docstring.dedent_interpd
+    def secondary_xaxis(self, location, *, functions=None, **kwargs):
+        """
+        Add a second x-axis to this axes.
+
+        For example if we want to have a second scale for the data plotted on
+        the xaxis.
+
+        %(_secax_docstring)s
+
+        Examples
+        --------
+        The main axis shows frequency, and the secondary axis shows period.
+
+        .. plot::
+
+            fig, ax = plt.subplots()
+            ax.loglog(range(1, 360, 5), range(1, 360, 5))
+            ax.set_xlabel('frequency [Hz]')
+
+            def invert(x):
+                return 1 / x
+
+            secax = ax.secondary_xaxis('top', functions=(invert, invert))
+            secax.set_xlabel('Period [s]')
+            plt.show()
+        """
+        if (location in ['top', 'bottom'] or isinstance(location, Number)):
+            secondary_ax = SecondaryAxis(self, 'x', location, functions,
+                                         **kwargs)
+            self.add_child_axes(secondary_ax)
+            return secondary_ax
+        else:
+            raise ValueError('secondary_xaxis location must be either '
+                             'a float or "top"/"bottom"')
+
+    def secondary_yaxis(self, location, *, functions=None, **kwargs):
+        """
+        Add a second y-axis to this axes.
+
+        For example if we want to have a second scale for the data plotted on
+        the yaxis.
+
+        %(_secax_docstring)s
+
+        Examples
+        --------
+        Add a secondary axes that converts from radians to degrees
+
+        .. plot::
+
+            fig, ax = plt.subplots()
+            ax.plot(range(1, 360, 5), range(1, 360, 5))
+            ax.set_ylabel('degrees')
+            secax = ax.secondary_yaxis('right', functions=(np.deg2rad,
+                                                           np.rad2deg))
+            secax.set_ylabel('radians')
+        """
+        if location in ['left', 'right'] or isinstance(location, Number):
+            secondary_ax = SecondaryAxis(self, 'y', location,
+                                         functions, **kwargs)
+            self.add_child_axes(secondary_ax)
+            return secondary_ax
+        else:
+            raise ValueError('secondary_yaxis location must be either '
+                             'a float or "left"/"right"')
+
+    @cbook._delete_parameter("3.1", "withdash")
+    def text(self, x, y, s, fontdict=None, withdash=False, **kwargs):
+        """
+        Add text to the axes.
+
+        Add the text *s* to the axes at location *x*, *y* in data coordinates.
+
+        Parameters
+        ----------
+        x, y : scalars
+            The position to place the text. By default, this is in data
+            coordinates. The coordinate system can be changed using the
+            *transform* parameter.
+
+        s : str
+            The text.
+
+        fontdict : dictionary, optional, default: None
+            A dictionary to override the default text properties. If fontdict
+            is None, the defaults are determined by your rc parameters.
+
+        withdash : boolean, optional, default: False
+            Creates a `~matplotlib.text.TextWithDash` instance instead of a
+            `~matplotlib.text.Text` instance.
+
+        Returns
+        -------
+        text : `.Text`
+            The created `.Text` instance.
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.text.Text` properties.
+            Other miscellaneous text parameters.
+
+        Examples
+        --------
+        Individual keyword arguments can be used to override any given
+        parameter::
+
+            >>> text(x, y, s, fontsize=12)
+
+        The default transform specifies that text is in data coords,
+        alternatively, you can specify text in axis coords ((0, 0) is
+        lower-left and (1, 1) is upper-right).  The example below places
+        text in the center of the axes::
+
+            >>> text(0.5, 0.5, 'matplotlib', horizontalalignment='center',
+            ...      verticalalignment='center', transform=ax.transAxes)
+
+        You can put a rectangular box around the text instance (e.g., to
+        set a background color) by using the keyword *bbox*.  *bbox* is
+        a dictionary of `~matplotlib.patches.Rectangle`
+        properties.  For example::
+
+            >>> text(x, y, s, bbox=dict(facecolor='red', alpha=0.5))
+        """
+        if fontdict is None:
+            fontdict = {}
+
+        effective_kwargs = {
+            'verticalalignment': 'baseline',
+            'horizontalalignment': 'left',
+            'transform': self.transData,
+            'clip_on': False,
+            **fontdict,
+            **kwargs,
+        }
+
+        # At some point if we feel confident that TextWithDash
+        # is robust as a drop-in replacement for Text and that
+        # the performance impact of the heavier-weight class
+        # isn't too significant, it may make sense to eliminate
+        # the withdash kwarg and simply delegate whether there's
+        # a dash to TextWithDash and dashlength.
+
+        if (withdash
+                and withdash is not cbook.deprecation._deprecated_parameter):
+            t = mtext.TextWithDash(x, y, text=s)
+        else:
+            t = mtext.Text(x, y, text=s)
+        t.update(effective_kwargs)
+
+        t.set_clip_path(self.patch)
+        self._add_text(t)
+        return t
+
+    @docstring.dedent_interpd
+    def annotate(self, s, xy, *args, **kwargs):
+        a = mtext.Annotation(s, xy, *args, **kwargs)
+        a.set_transform(mtransforms.IdentityTransform())
+        if 'clip_on' in kwargs:
+            a.set_clip_path(self.patch)
+        self._add_text(a)
+        return a
+    annotate.__doc__ = mtext.Annotation.__init__.__doc__
+    #### Lines and spans
+
+    @docstring.dedent_interpd
+    def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
+        """
+        Add a horizontal line across the axis.
+
+        Parameters
+        ----------
+        y : scalar, optional, default: 0
+            y position in data coordinates of the horizontal line.
+
+        xmin : scalar, optional, default: 0
+            Should be between 0 and 1, 0 being the far left of the plot, 1 the
+            far right of the plot.
+
+        xmax : scalar, optional, default: 1
+            Should be between 0 and 1, 0 being the far left of the plot, 1 the
+            far right of the plot.
+
+        Returns
+        -------
+        line : `~matplotlib.lines.Line2D`
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Valid keyword arguments are `.Line2D` properties, with the
+            exception of 'transform':
+
+            %(_Line2D_docstr)s
+
+        See also
+        --------
+        hlines : Add horizontal lines in data coordinates.
+        axhspan : Add a horizontal span (rectangle) across the axis.
+
+        Examples
+        --------
+        * draw a thick red hline at 'y' = 0 that spans the xrange::
+
+            >>> axhline(linewidth=4, color='r')
+
+        * draw a default hline at 'y' = 1 that spans the xrange::
+
+            >>> axhline(y=1)
+
+        * draw a default hline at 'y' = .5 that spans the middle half of
+          the xrange::
+
+            >>> axhline(y=.5, xmin=0.25, xmax=0.75)
+        """
+        if "transform" in kwargs:
+            raise ValueError(
+                "'transform' is not allowed as a kwarg;"
+                + "axhline generates its own transform.")
+        ymin, ymax = self.get_ybound()
+
+        # We need to strip away the units for comparison with
+        # non-unitized bounds
+        self._process_unit_info(ydata=y, kwargs=kwargs)
+        yy = self.convert_yunits(y)
+        scaley = (yy < ymin) or (yy > ymax)
+
+        trans = self.get_yaxis_transform(which='grid')
+        l = mlines.Line2D([xmin, xmax], [y, y], transform=trans, **kwargs)
+        self.add_line(l)
+        self._request_autoscale_view(scalex=False, scaley=scaley)
+        return l
+
+    @docstring.dedent_interpd
+    def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
+        """
+        Add a vertical line across the axes.
+
+        Parameters
+        ----------
+        x : scalar, optional, default: 0
+            x position in data coordinates of the vertical line.
+
+        ymin : scalar, optional, default: 0
+            Should be between 0 and 1, 0 being the bottom of the plot, 1 the
+            top of the plot.
+
+        ymax : scalar, optional, default: 1
+            Should be between 0 and 1, 0 being the bottom of the plot, 1 the
+            top of the plot.
+
+        Returns
+        -------
+        line : `~matplotlib.lines.Line2D`
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Valid keyword arguments are `.Line2D` properties, with the
+            exception of 'transform':
+
+            %(_Line2D_docstr)s
+
+        Examples
+        --------
+        * draw a thick red vline at *x* = 0 that spans the yrange::
+
+            >>> axvline(linewidth=4, color='r')
+
+        * draw a default vline at *x* = 1 that spans the yrange::
+
+            >>> axvline(x=1)
+
+        * draw a default vline at *x* = .5 that spans the middle half of
+          the yrange::
+
+            >>> axvline(x=.5, ymin=0.25, ymax=0.75)
+
+        See also
+        --------
+        vlines : Add vertical lines in data coordinates.
+        axvspan : Add a vertical span (rectangle) across the axis.
+        """
+
+        if "transform" in kwargs:
+            raise ValueError(
+                "'transform' is not allowed as a kwarg;"
+                + "axvline generates its own transform.")
+        xmin, xmax = self.get_xbound()
+
+        # We need to strip away the units for comparison with
+        # non-unitized bounds
+        self._process_unit_info(xdata=x, kwargs=kwargs)
+        xx = self.convert_xunits(x)
+        scalex = (xx < xmin) or (xx > xmax)
+
+        trans = self.get_xaxis_transform(which='grid')
+        l = mlines.Line2D([x, x], [ymin, ymax], transform=trans, **kwargs)
+        self.add_line(l)
+        self._request_autoscale_view(scalex=scalex, scaley=False)
+        return l
+
+    @docstring.dedent_interpd
+    def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
+        """
+        Add a horizontal span (rectangle) across the axis.
+
+        Draw a horizontal span (rectangle) from *ymin* to *ymax*.
+        With the default values of *xmin* = 0 and *xmax* = 1, this
+        always spans the xrange, regardless of the xlim settings, even
+        if you change them, e.g., with the :meth:`set_xlim` command.
+        That is, the horizontal extent is in axes coords: 0=left,
+        0.5=middle, 1.0=right but the *y* location is in data
+        coordinates.
+
+        Parameters
+        ----------
+        ymin : float
+               Lower limit of the horizontal span in data units.
+        ymax : float
+               Upper limit of the horizontal span in data units.
+        xmin : float, optional, default: 0
+               Lower limit of the vertical span in axes (relative
+               0-1) units.
+        xmax : float, optional, default: 1
+               Upper limit of the vertical span in axes (relative
+               0-1) units.
+
+        Returns
+        -------
+        Polygon : `~matplotlib.patches.Polygon`
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.patches.Polygon` properties.
+
+        %(Polygon)s
+
+        See Also
+        --------
+        axvspan : Add a vertical span across the axes.
+        """
+        trans = self.get_yaxis_transform(which='grid')
+
+        # process the unit information
+        self._process_unit_info([xmin, xmax], [ymin, ymax], kwargs=kwargs)
+
+        # first we need to strip away the units
+        xmin, xmax = self.convert_xunits([xmin, xmax])
+        ymin, ymax = self.convert_yunits([ymin, ymax])
+
+        verts = (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)
+        p = mpatches.Polygon(verts, **kwargs)
+        p.set_transform(trans)
+        self.add_patch(p)
+        self._request_autoscale_view(scalex=False)
+        return p
+
+    def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
+        """
+        Add a vertical span (rectangle) across the axes.
+
+        Draw a vertical span (rectangle) from *xmin* to *xmax*.  With
+        the default values of *ymin* = 0 and *ymax* = 1. This always
+        spans the yrange, regardless of the ylim settings, even if you
+        change them, e.g., with the :meth:`set_ylim` command.  That is,
+        the vertical extent is in axes coords: 0=bottom, 0.5=middle,
+        1.0=top but the x location is in data coordinates.
+
+        Parameters
+        ----------
+        xmin : scalar
+            Number indicating the first X-axis coordinate of the vertical
+            span rectangle in data units.
+        xmax : scalar
+            Number indicating the second X-axis coordinate of the vertical
+            span rectangle in data units.
+        ymin : scalar, optional
+            Number indicating the first Y-axis coordinate of the vertical
+            span rectangle in relative Y-axis units (0-1). Default to 0.
+        ymax : scalar, optional
+            Number indicating the second Y-axis coordinate of the vertical
+            span rectangle in relative Y-axis units (0-1). Default to 1.
+
+        Returns
+        -------
+        rectangle : `~matplotlib.patches.Polygon`
+            Vertical span (rectangle) from (xmin, ymin) to (xmax, ymax).
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Optional parameters are properties of the class `.Polygon`.
+
+        See Also
+        --------
+        axhspan : Add a horizontal span across the axes.
+
+        Examples
+        --------
+        Draw a vertical, green, translucent rectangle from x = 1.25 to
+        x = 1.55 that spans the yrange of the axes.
+
+        >>> axvspan(1.25, 1.55, facecolor='g', alpha=0.5)
+
+        """
+        trans = self.get_xaxis_transform(which='grid')
+
+        # process the unit information
+        self._process_unit_info([xmin, xmax], [ymin, ymax], kwargs=kwargs)
+
+        # first we need to strip away the units
+        xmin, xmax = self.convert_xunits([xmin, xmax])
+        ymin, ymax = self.convert_yunits([ymin, ymax])
+
+        verts = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
+        p = mpatches.Polygon(verts, **kwargs)
+        p.set_transform(trans)
+        self.add_patch(p)
+        self._request_autoscale_view(scaley=False)
+        return p
+
+    @_preprocess_data(replace_names=["y", "xmin", "xmax", "colors"],
+                      label_namer="y")
+    def hlines(self, y, xmin, xmax, colors='k', linestyles='solid',
+               label='', **kwargs):
+        """
+        Plot horizontal lines at each *y* from *xmin* to *xmax*.
+
+        Parameters
+        ----------
+        y : scalar or sequence of scalar
+            y-indexes where to plot the lines.
+
+        xmin, xmax : scalar or 1D array-like
+            Respective beginning and end of each line. If scalars are
+            provided, all lines will have same length.
+
+        colors : array-like of colors, optional, default: 'k'
+
+        linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional
+
+        label : str, optional, default: ''
+
+        Returns
+        -------
+        lines : `~matplotlib.collections.LineCollection`
+
+        Other Parameters
+        ----------------
+        **kwargs :  `~matplotlib.collections.LineCollection` properties.
+
+        See also
+        --------
+        vlines : vertical lines
+        axhline: horizontal line across the axes
+        """
+
+        # We do the conversion first since not all unitized data is uniform
+        # process the unit information
+        self._process_unit_info([xmin, xmax], y, kwargs=kwargs)
+        y = self.convert_yunits(y)
+        xmin = self.convert_xunits(xmin)
+        xmax = self.convert_xunits(xmax)
+
+        if not np.iterable(y):
+            y = [y]
+        if not np.iterable(xmin):
+            xmin = [xmin]
+        if not np.iterable(xmax):
+            xmax = [xmax]
+
+        y, xmin, xmax = cbook.delete_masked_points(y, xmin, xmax)
+
+        y = np.ravel(y)
+        xmin = np.resize(xmin, y.shape)
+        xmax = np.resize(xmax, y.shape)
+
+        verts = [((thisxmin, thisy), (thisxmax, thisy))
+                 for thisxmin, thisxmax, thisy in zip(xmin, xmax, y)]
+        lines = mcoll.LineCollection(verts, colors=colors,
+                                     linestyles=linestyles, label=label)
+        self.add_collection(lines, autolim=False)
+        lines.update(kwargs)
+
+        if len(y) > 0:
+            minx = min(xmin.min(), xmax.min())
+            maxx = max(xmin.max(), xmax.max())
+            miny = y.min()
+            maxy = y.max()
+
+            corners = (minx, miny), (maxx, maxy)
+
+            self.update_datalim(corners)
+            self._request_autoscale_view()
+
+        return lines
+
+    @_preprocess_data(replace_names=["x", "ymin", "ymax", "colors"],
+                      label_namer="x")
+    def vlines(self, x, ymin, ymax, colors='k', linestyles='solid',
+               label='', **kwargs):
+        """
+        Plot vertical lines.
+
+        Plot vertical lines at each *x* from *ymin* to *ymax*.
+
+        Parameters
+        ----------
+        x : scalar or 1D array-like
+            x-indexes where to plot the lines.
+
+        ymin, ymax : scalar or 1D array-like
+            Respective beginning and end of each line. If scalars are
+            provided, all lines will have same length.
+
+        colors : array-like of colors, optional, default: 'k'
+
+        linestyles : {'solid', 'dashed', 'dashdot', 'dotted'}, optional
+
+        label : str, optional, default: ''
+
+        Returns
+        -------
+        lines : `~matplotlib.collections.LineCollection`
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.collections.LineCollection` properties.
+
+        See also
+        --------
+        hlines : horizontal lines
+        axvline: vertical line across the axes
+        """
+
+        self._process_unit_info(xdata=x, ydata=[ymin, ymax], kwargs=kwargs)
+
+        # We do the conversion first since not all unitized data is uniform
+        x = self.convert_xunits(x)
+        ymin = self.convert_yunits(ymin)
+        ymax = self.convert_yunits(ymax)
+
+        if not np.iterable(x):
+            x = [x]
+        if not np.iterable(ymin):
+            ymin = [ymin]
+        if not np.iterable(ymax):
+            ymax = [ymax]
+
+        x, ymin, ymax = cbook.delete_masked_points(x, ymin, ymax)
+
+        x = np.ravel(x)
+        ymin = np.resize(ymin, x.shape)
+        ymax = np.resize(ymax, x.shape)
+
+        verts = [((thisx, thisymin), (thisx, thisymax))
+                 for thisx, thisymin, thisymax in zip(x, ymin, ymax)]
+        lines = mcoll.LineCollection(verts, colors=colors,
+                                     linestyles=linestyles, label=label)
+        self.add_collection(lines, autolim=False)
+        lines.update(kwargs)
+
+        if len(x) > 0:
+            minx = x.min()
+            maxx = x.max()
+            miny = min(ymin.min(), ymax.min())
+            maxy = max(ymin.max(), ymax.max())
+
+            corners = (minx, miny), (maxx, maxy)
+            self.update_datalim(corners)
+            self._request_autoscale_view()
+
+        return lines
+
+    @_preprocess_data(replace_names=["positions", "lineoffsets",
+                                     "linelengths", "linewidths",
+                                     "colors", "linestyles"])
+    @docstring.dedent_interpd
+    def eventplot(self, positions, orientation='horizontal', lineoffsets=1,
+                  linelengths=1, linewidths=None, colors=None,
+                  linestyles='solid', **kwargs):
+        """
+        Plot identical parallel lines at the given positions.
+
+        *positions* should be a 1D or 2D array-like object, with each row
+        corresponding to a row or column of lines.
+
+        This type of plot is commonly used in neuroscience for representing
+        neural events, where it is usually called a spike raster, dot raster,
+        or raster plot.
+
+        However, it is useful in any situation where you wish to show the
+        timing or position of multiple sets of discrete events, such as the
+        arrival times of people to a business on each day of the month or the
+        date of hurricanes each year of the last century.
+
+        Parameters
+        ----------
+        positions : 1D or 2D array-like object
+            Each value is an event. If *positions* is a 2D array-like, each
+            row corresponds to a row or a column of lines (depending on the
+            *orientation* parameter).
+
+        orientation : {'horizontal', 'vertical'}, optional
+            Controls the direction of the event collections:
+
+                - 'horizontal' : the lines are arranged horizontally in rows,
+                  and are vertical.
+                - 'vertical' : the lines are arranged vertically in columns,
+                  and are horizontal.
+
+        lineoffsets : scalar or sequence of scalars, optional, default: 1
+            The offset of the center of the lines from the origin, in the
+            direction orthogonal to *orientation*.
+
+        linelengths : scalar or sequence of scalars, optional, default: 1
+            The total height of the lines (i.e. the lines stretches from
+            ``lineoffset - linelength/2`` to ``lineoffset + linelength/2``).
+
+        linewidths : scalar, scalar sequence or None, optional, default: None
+            The line width(s) of the event lines, in points. If it is None,
+            defaults to its rcParams setting.
+
+        colors : color, sequence of colors or None, optional, default: None
+            The color(s) of the event lines. If it is None, defaults to its
+            rcParams setting.
+
+        linestyles : str or tuple or a sequence of such values, optional
+            Default is 'solid'. Valid strings are ['solid', 'dashed',
+            'dashdot', 'dotted', '-', '--', '-.', ':']. Dash tuples
+            should be of the form::
+
+                (offset, onoffseq),
+
+            where *onoffseq* is an even length tuple of on and off ink
+            in points.
+
+        **kwargs : optional
+            Other keyword arguments are line collection properties.  See
+            :class:`~matplotlib.collections.LineCollection` for a list of
+            the valid properties.
+
+        Returns
+        -------
+        list : A list of :class:`~.collections.EventCollection` objects.
+            Contains the :class:`~.collections.EventCollection` that
+            were added.
+
+        Notes
+        -----
+        For *linelengths*, *linewidths*, *colors*, and *linestyles*, if only
+        a single value is given, that value is applied to all lines.  If an
+        array-like is given, it must have the same length as *positions*, and
+        each value will be applied to the corresponding row of the array.
+
+        Examples
+        --------
+        .. plot:: gallery/lines_bars_and_markers/eventplot_demo.py
+        """
+        self._process_unit_info(xdata=positions,
+                                ydata=[lineoffsets, linelengths],
+                                kwargs=kwargs)
+
+        # We do the conversion first since not all unitized data is uniform
+        positions = self.convert_xunits(positions)
+        lineoffsets = self.convert_yunits(lineoffsets)
+        linelengths = self.convert_yunits(linelengths)
+
+        if not np.iterable(positions):
+            positions = [positions]
+        elif any(np.iterable(position) for position in positions):
+            positions = [np.asanyarray(position) for position in positions]
+        else:
+            positions = [np.asanyarray(positions)]
+
+        if len(positions) == 0:
+            return []
+
+        # prevent 'singular' keys from **kwargs dict from overriding the effect
+        # of 'plural' keyword arguments (e.g. 'color' overriding 'colors')
+        colors = cbook.local_over_kwdict(colors, kwargs, 'color')
+        linewidths = cbook.local_over_kwdict(linewidths, kwargs, 'linewidth')
+        linestyles = cbook.local_over_kwdict(linestyles, kwargs, 'linestyle')
+
+        if not np.iterable(lineoffsets):
+            lineoffsets = [lineoffsets]
+        if not np.iterable(linelengths):
+            linelengths = [linelengths]
+        if not np.iterable(linewidths):
+            linewidths = [linewidths]
+        if not np.iterable(colors):
+            colors = [colors]
+        if hasattr(linestyles, 'lower') or not np.iterable(linestyles):
+            linestyles = [linestyles]
+
+        lineoffsets = np.asarray(lineoffsets)
+        linelengths = np.asarray(linelengths)
+        linewidths = np.asarray(linewidths)
+
+        if len(lineoffsets) == 0:
+            lineoffsets = [None]
+        if len(linelengths) == 0:
+            linelengths = [None]
+        if len(linewidths) == 0:
+            lineoffsets = [None]
+        if len(linewidths) == 0:
+            lineoffsets = [None]
+        if len(colors) == 0:
+            colors = [None]
+        try:
+            # Early conversion of the colors into RGBA values to take care
+            # of cases like colors='0.5' or colors='C1'.  (Issue #8193)
+            colors = mcolors.to_rgba_array(colors)
+        except ValueError:
+            # Will fail if any element of *colors* is None. But as long
+            # as len(colors) == 1 or len(positions), the rest of the
+            # code should process *colors* properly.
+            pass
+
+        if len(lineoffsets) == 1 and len(positions) != 1:
+            lineoffsets = np.tile(lineoffsets, len(positions))
+            lineoffsets[0] = 0
+            lineoffsets = np.cumsum(lineoffsets)
+        if len(linelengths) == 1:
+            linelengths = np.tile(linelengths, len(positions))
+        if len(linewidths) == 1:
+            linewidths = np.tile(linewidths, len(positions))
+        if len(colors) == 1:
+            colors = list(colors)
+            colors = colors * len(positions)
+        if len(linestyles) == 1:
+            linestyles = [linestyles] * len(positions)
+
+        if len(lineoffsets) != len(positions):
+            raise ValueError('lineoffsets and positions are unequal sized '
+                             'sequences')
+        if len(linelengths) != len(positions):
+            raise ValueError('linelengths and positions are unequal sized '
+                             'sequences')
+        if len(linewidths) != len(positions):
+            raise ValueError('linewidths and positions are unequal sized '
+                             'sequences')
+        if len(colors) != len(positions):
+            raise ValueError('colors and positions are unequal sized '
+                             'sequences')
+        if len(linestyles) != len(positions):
+            raise ValueError('linestyles and positions are unequal sized '
+                             'sequences')
+
+        colls = []
+        for position, lineoffset, linelength, linewidth, color, linestyle in \
+            zip(positions, lineoffsets, linelengths, linewidths,
+                           colors, linestyles):
+            coll = mcoll.EventCollection(position,
+                                         orientation=orientation,
+                                         lineoffset=lineoffset,
+                                         linelength=linelength,
+                                         linewidth=linewidth,
+                                         color=color,
+                                         linestyle=linestyle)
+            self.add_collection(coll, autolim=False)
+            coll.update(kwargs)
+            colls.append(coll)
+
+        if len(positions) > 0:
+            # try to get min/max
+            min_max = [(np.min(_p), np.max(_p)) for _p in positions
+                       if len(_p) > 0]
+            # if we have any non-empty positions, try to autoscale
+            if len(min_max) > 0:
+                mins, maxes = zip(*min_max)
+                minpos = np.min(mins)
+                maxpos = np.max(maxes)
+
+                minline = (lineoffsets - linelengths).min()
+                maxline = (lineoffsets + linelengths).max()
+
+                if (orientation is not None and
+                        orientation.lower() == "vertical"):
+                    corners = (minline, minpos), (maxline, maxpos)
+                else:  # "horizontal", None or "none" (see EventCollection)
+                    corners = (minpos, minline), (maxpos, maxline)
+                self.update_datalim(corners)
+                self._request_autoscale_view()
+
+        return colls
+
+    #### Basic plotting
+
+    # Uses a custom implementation of data-kwarg handling in
+    # _process_plot_var_args.
+    @docstring.dedent_interpd
+    def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs):
+        """
+        Plot y versus x as lines and/or markers.
+
+        Call signatures::
+
+            plot([x], y, [fmt], *, data=None, **kwargs)
+            plot([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)
+
+        The coordinates of the points or line nodes are given by *x*, *y*.
+
+        The optional parameter *fmt* is a convenient way for defining basic
+        formatting like color, marker and linestyle. It's a shortcut string
+        notation described in the *Notes* section below.
+
+        >>> plot(x, y)        # plot x and y using default line style and color
+        >>> plot(x, y, 'bo')  # plot x and y using blue circle markers
+        >>> plot(y)           # plot y using x as index array 0..N-1
+        >>> plot(y, 'r+')     # ditto, but with red plusses
+
+        You can use `.Line2D` properties as keyword arguments for more
+        control on the appearance. Line properties and *fmt* can be mixed.
+        The following two calls yield identical results:
+
+        >>> plot(x, y, 'go--', linewidth=2, markersize=12)
+        >>> plot(x, y, color='green', marker='o', linestyle='dashed',
+        ...      linewidth=2, markersize=12)
+
+        When conflicting with *fmt*, keyword arguments take precedence.
+
+
+        **Plotting labelled data**
+
+        There's a convenient way for plotting objects with labelled data (i.e.
+        data that can be accessed by index ``obj['y']``). Instead of giving
+        the data in *x* and *y*, you can provide the object in the *data*
+        parameter and just give the labels for *x* and *y*::
+
+        >>> plot('xlabel', 'ylabel', data=obj)
+
+        All indexable objects are supported. This could e.g. be a `dict`, a
+        `pandas.DataFame` or a structured numpy array.
+
+
+        **Plotting multiple sets of data**
+
+        There are various ways to plot multiple sets of data.
+
+        - The most straight forward way is just to call `plot` multiple times.
+          Example:
+
+          >>> plot(x1, y1, 'bo')
+          >>> plot(x2, y2, 'go')
+
+        - Alternatively, if your data is already a 2d array, you can pass it
+          directly to *x*, *y*. A separate data set will be drawn for every
+          column.
+
+          Example: an array ``a`` where the first column represents the *x*
+          values and the other columns are the *y* columns::
+
+          >>> plot(a[0], a[1:])
+
+        - The third way is to specify multiple sets of *[x]*, *y*, *[fmt]*
+          groups::
+
+          >>> plot(x1, y1, 'g^', x2, y2, 'g-')
+
+          In this case, any additional keyword argument applies to all
+          datasets. Also this syntax cannot be combined with the *data*
+          parameter.
+
+        By default, each line is assigned a different style specified by a
+        'style cycle'. The *fmt* and line property parameters are only
+        necessary if you want explicit deviations from these defaults.
+        Alternatively, you can also change the style cycle using
+        :rc:`axes.prop_cycle`.
+
+
+        Parameters
+        ----------
+        x, y : array-like or scalar
+            The horizontal / vertical coordinates of the data points.
+            *x* values are optional and default to `range(len(y))`.
+
+            Commonly, these parameters are 1D arrays.
+
+            They can also be scalars, or two-dimensional (in that case, the
+            columns represent separate data sets).
+
+            These arguments cannot be passed as keywords.
+
+        fmt : str, optional
+            A format string, e.g. 'ro' for red circles. See the *Notes*
+            section for a full description of the format strings.
+
+            Format strings are just an abbreviation for quickly setting
+            basic line properties. All of these and more can also be
+            controlled by keyword arguments.
+
+            This argument cannot be passed as keyword.
+
+        data : indexable object, optional
+            An object with labelled data. If given, provide the label names to
+            plot in *x* and *y*.
+
+            .. note::
+                Technically there's a slight ambiguity in calls where the
+                second label is a valid *fmt*. `plot('n', 'o', data=obj)`
+                could be `plt(x, y)` or `plt(y, fmt)`. In such cases,
+                the former interpretation is chosen, but a warning is issued.
+                You may suppress the warning by adding an empty format string
+                `plot('n', 'o', '', data=obj)`.
+
+        Other Parameters
+        ----------------
+        scalex, scaley : bool, optional, default: True
+            These parameters determined if the view limits are adapted to
+            the data limits. The values are passed on to `autoscale_view`.
+
+        **kwargs : `.Line2D` properties, optional
+            *kwargs* are used to specify properties like a line label (for
+            auto legends), linewidth, antialiasing, marker face color.
+            Example::
+
+            >>> plot([1, 2, 3], [1, 2, 3], 'go-', label='line 1', linewidth=2)
+            >>> plot([1, 2, 3], [1, 4, 9], 'rs', label='line 2')
+
+            If you make multiple lines with one plot command, the kwargs
+            apply to all those lines.
+
+            Here is a list of available `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        Returns
+        -------
+        lines
+            A list of `.Line2D` objects representing the plotted data.
+
+        See Also
+        --------
+        scatter : XY scatter plot with markers of varying size and/or color (
+            sometimes also called bubble chart).
+
+        Notes
+        -----
+        **Format Strings**
+
+        A format string consists of a part for color, marker and line::
+
+            fmt = '[marker][line][color]'
+
+        Each of them is optional. If not provided, the value from the style
+        cycle is used. Exception: If ``line`` is given, but no ``marker``,
+        the data will be a line without markers.
+
+        Other combinations such as ``[color][marker][line]`` are also
+        supported, but note that their parsing may be ambiguous.
+
+        **Markers**
+
+        =============    ===============================
+        character        description
+        =============    ===============================
+        ``'.'``          point marker
+        ``','``          pixel marker
+        ``'o'``          circle marker
+        ``'v'``          triangle_down marker
+        ``'^'``          triangle_up marker
+        ``'<'``          triangle_left marker
+        ``'>'``          triangle_right marker
+        ``'1'``          tri_down marker
+        ``'2'``          tri_up marker
+        ``'3'``          tri_left marker
+        ``'4'``          tri_right marker
+        ``'s'``          square marker
+        ``'p'``          pentagon marker
+        ``'*'``          star marker
+        ``'h'``          hexagon1 marker
+        ``'H'``          hexagon2 marker
+        ``'+'``          plus marker
+        ``'x'``          x marker
+        ``'D'``          diamond marker
+        ``'d'``          thin_diamond marker
+        ``'|'``          vline marker
+        ``'_'``          hline marker
+        =============    ===============================
+
+        **Line Styles**
+
+        =============    ===============================
+        character        description
+        =============    ===============================
+        ``'-'``          solid line style
+        ``'--'``         dashed line style
+        ``'-.'``         dash-dot line style
+        ``':'``          dotted line style
+        =============    ===============================
+
+        Example format strings::
+
+            'b'    # blue markers with default shape
+            'or'   # red circles
+            '-g'   # green solid line
+            '--'   # dashed line with default color
+            '^k:'  # black triangle_up markers connected by a dotted line
+
+        **Colors**
+
+        The supported color abbreviations are the single letter codes
+
+        =============    ===============================
+        character        color
+        =============    ===============================
+        ``'b'``          blue
+        ``'g'``          green
+        ``'r'``          red
+        ``'c'``          cyan
+        ``'m'``          magenta
+        ``'y'``          yellow
+        ``'k'``          black
+        ``'w'``          white
+        =============    ===============================
+
+        and the ``'CN'`` colors that index into the default property cycle.
+
+        If the color is the only part of the format string, you can
+        additionally use any  `matplotlib.colors` spec, e.g. full names
+        (``'green'``) or hex strings (``'#008000'``).
+        """
+        kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
+        lines = [*self._get_lines(*args, data=data, **kwargs)]
+        for line in lines:
+            self.add_line(line)
+        self._request_autoscale_view(scalex=scalex, scaley=scaley)
+        return lines
+
+    @_preprocess_data(replace_names=["x", "y"], label_namer="y")
+    @docstring.dedent_interpd
+    def plot_date(self, x, y, fmt='o', tz=None, xdate=True, ydate=False,
+                  **kwargs):
+        """
+        Plot data that contains dates.
+
+        Similar to `.plot`, this plots *y* vs. *x* as lines or markers.
+        However, the axis labels are formatted as dates depending on *xdate*
+        and *ydate*.
+
+        Parameters
+        ----------
+        x, y : array-like
+            The coordinates of the data points. If *xdate* or *ydate* is
+            *True*, the respective values *x* or *y* are interpreted as
+            :ref:`Matplotlib dates <date-format>`.
+
+        fmt : str, optional
+            The plot format string. For details, see the corresponding
+            parameter in `.plot`.
+
+        tz : timezone string or `tzinfo` or None
+            The time zone to use in labeling dates. If *None*, defaults to
+            :rc:`timezone`.
+
+        xdate : bool, optional, default: True
+            If *True*, the *x*-axis will be interpreted as Matplotlib dates.
+
+        ydate : bool, optional, default: False
+            If *True*, the *y*-axis will be interpreted as Matplotlib dates.
+
+
+        Returns
+        -------
+        lines
+            A list of `.Line2D` objects representing the plotted data.
+
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        See Also
+        --------
+        matplotlib.dates : Helper functions on dates.
+        matplotlib.dates.date2num : Convert dates to num.
+        matplotlib.dates.num2date : Convert num to dates.
+        matplotlib.dates.drange : Create an equally spaced sequence of dates.
+
+        Notes
+        -----
+        If you are using custom date tickers and formatters, it may be
+        necessary to set the formatters/locators after the call to
+        `.plot_date`. `.plot_date` will set the default tick locator to
+        `.AutoDateLocator` (if the tick locator is not already set to a
+        `.DateLocator` instance) and the default tick formatter to
+        `.AutoDateFormatter` (if the tick formatter is not already set to a
+        `.DateFormatter` instance).
+        """
+        if xdate:
+            self.xaxis_date(tz)
+        if ydate:
+            self.yaxis_date(tz)
+
+        ret = self.plot(x, y, fmt, **kwargs)
+
+        self._request_autoscale_view()
+
+        return ret
+
+    # @_preprocess_data() # let 'plot' do the unpacking..
+    @docstring.dedent_interpd
+    def loglog(self, *args, **kwargs):
+        """
+        Make a plot with log scaling on both the x and y axis.
+
+        Call signatures::
+
+            loglog([x], y, [fmt], data=None, **kwargs)
+            loglog([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)
+
+        This is just a thin wrapper around `.plot` which additionally changes
+        both the x-axis and the y-axis to log scaling. All of the concepts and
+        parameters of plot can be used here as well.
+
+        The additional parameters *basex/y*, *subsx/y* and *nonposx/y* control
+        the x/y-axis properties. They are just forwarded to `.Axes.set_xscale`
+        and `.Axes.set_yscale`.
+
+        Parameters
+        ----------
+        basex, basey : scalar, optional, default 10
+            Base of the x/y logarithm.
+
+        subsx, subsy : sequence, optional
+            The location of the minor x/y ticks. If *None*, reasonable
+            locations are automatically chosen depending on the number of
+            decades in the plot.
+            See `.Axes.set_xscale` / `.Axes.set_yscale` for details.
+
+        nonposx, nonposy : {'mask', 'clip'}, optional, default 'mask'
+            Non-positive values in x or y can be masked as invalid, or clipped
+            to a very small positive number.
+
+        Returns
+        -------
+        lines
+            A list of `.Line2D` objects representing the plotted data.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            All parameters supported by `.plot`.
+        """
+        dx = {k: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx']
+              if k in kwargs}
+        dy = {k: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy']
+              if k in kwargs}
+
+        self.set_xscale('log', **dx)
+        self.set_yscale('log', **dy)
+
+        l = self.plot(*args, **kwargs)
+        return l
+
+    # @_preprocess_data() # let 'plot' do the unpacking..
+    @docstring.dedent_interpd
+    def semilogx(self, *args, **kwargs):
+        """
+        Make a plot with log scaling on the x axis.
+
+        Call signatures::
+
+            semilogx([x], y, [fmt], data=None, **kwargs)
+            semilogx([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)
+
+        This is just a thin wrapper around `.plot` which additionally changes
+        the x-axis to log scaling. All of the concepts and parameters of plot
+        can be used here as well.
+
+        The additional parameters *basex*, *subsx* and *nonposx* control the
+        x-axis properties. They are just forwarded to `.Axes.set_xscale`.
+
+        Parameters
+        ----------
+        basex : scalar, optional, default 10
+            Base of the x logarithm.
+
+        subsx : array-like, optional
+            The location of the minor xticks. If *None*, reasonable locations
+            are automatically chosen depending on the number of decades in the
+            plot. See `.Axes.set_xscale` for details.
+
+        nonposx : {'mask', 'clip'}, optional, default 'mask'
+            Non-positive values in x can be masked as invalid, or clipped to a
+            very small positive number.
+
+        Returns
+        -------
+        lines
+            A list of `.Line2D` objects representing the plotted data.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            All parameters supported by `.plot`.
+        """
+        d = {k: kwargs.pop(k) for k in ['basex', 'subsx', 'nonposx']
+             if k in kwargs}
+
+        self.set_xscale('log', **d)
+        l = self.plot(*args, **kwargs)
+        return l
+
+    # @_preprocess_data() # let 'plot' do the unpacking..
+    @docstring.dedent_interpd
+    def semilogy(self, *args, **kwargs):
+        """
+        Make a plot with log scaling on the y axis.
+
+        Call signatures::
+
+            semilogy([x], y, [fmt], data=None, **kwargs)
+            semilogy([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)
+
+        This is just a thin wrapper around `.plot` which additionally changes
+        the y-axis to log scaling. All of the concepts and parameters of plot
+        can be used here as well.
+
+        The additional parameters *basey*, *subsy* and *nonposy* control the
+        y-axis properties. They are just forwarded to `.Axes.set_yscale`.
+
+        Parameters
+        ----------
+        basey : scalar, optional, default 10
+            Base of the y logarithm.
+
+        subsy : array-like, optional
+            The location of the minor yticks. If *None*, reasonable locations
+            are automatically chosen depending on the number of decades in the
+            plot. See `.Axes.set_yscale` for details.
+
+        nonposy : {'mask', 'clip'}, optional, default 'mask'
+            Non-positive values in y can be masked as invalid, or clipped to a
+            very small positive number.
+
+        Returns
+        -------
+        lines
+            A list of `.Line2D` objects representing the plotted data.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            All parameters supported by `.plot`.
+        """
+        d = {k: kwargs.pop(k) for k in ['basey', 'subsy', 'nonposy']
+             if k in kwargs}
+        self.set_yscale('log', **d)
+        l = self.plot(*args, **kwargs)
+
+        return l
+
+    @_preprocess_data(replace_names=["x"], label_namer="x")
+    def acorr(self, x, **kwargs):
+        """
+        Plot the autocorrelation of *x*.
+
+        Parameters
+        ----------
+        x : array-like
+
+        detrend : callable, optional, default: `mlab.detrend_none`
+            *x* is detrended by the *detrend* callable. This must be a
+            function ``x = detrend(x)`` accepting and returning an
+            `numpy.array`. Default is no normalization.
+
+        normed : bool, optional, default: True
+            If ``True``, input vectors are normalised to unit length.
+
+        usevlines : bool, optional, default: True
+            Determines the plot style.
+
+            If ``True``, vertical lines are plotted from 0 to the acorr value
+            using `Axes.vlines`. Additionally, a horizontal line is plotted
+            at y=0 using `Axes.axhline`.
+
+            If ``False``, markers are plotted at the acorr values using
+            `Axes.plot`.
+
+        maxlags : int, optional, default: 10
+            Number of lags to show. If ``None``, will return all
+            ``2 * len(x) - 1`` lags.
+
+        Returns
+        -------
+        lags : array (length ``2*maxlags+1``)
+            The lag vector.
+        c : array  (length ``2*maxlags+1``)
+            The auto correlation vector.
+        line : `.LineCollection` or `.Line2D`
+            `.Artist` added to the axes of the correlation:
+
+            - `.LineCollection` if *usevlines* is True.
+            - `.Line2D` if *usevlines* is False.
+        b : `.Line2D` or None
+            Horizontal line at 0 if *usevlines* is True
+            None *usevlines* is False.
+
+        Other Parameters
+        ----------------
+        linestyle : `.Line2D` property, optional
+            The linestyle for plotting the data points.
+            Only used if *usevlines* is ``False``.
+
+        marker : str, optional, default: 'o'
+            The marker for plotting the data points.
+            Only used if *usevlines* is ``False``.
+
+        Notes
+        -----
+        The cross correlation is performed with :func:`numpy.correlate` with
+        ``mode = "full"``.
+        """
+        return self.xcorr(x, x, **kwargs)
+
+    @_preprocess_data(replace_names=["x", "y"], label_namer="y")
+    def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none,
+              usevlines=True, maxlags=10, **kwargs):
+        r"""
+        Plot the cross correlation between *x* and *y*.
+
+        The correlation with lag k is defined as
+        :math:`\sum_n x[n+k] \cdot y^*[n]`, where :math:`y^*` is the complex
+        conjugate of :math:`y`.
+
+        Parameters
+        ----------
+        x : array-like of length n
+
+        y : array-like of length n
+
+        detrend : callable, optional, default: `mlab.detrend_none`
+            *x* and *y* are detrended by the *detrend* callable. This must be a
+            function ``x = detrend(x)`` accepting and returning an
+            `numpy.array`. Default is no normalization.
+
+        normed : bool, optional, default: True
+            If ``True``, input vectors are normalised to unit length.
+
+        usevlines : bool, optional, default: True
+            Determines the plot style.
+
+            If ``True``, vertical lines are plotted from 0 to the xcorr value
+            using `Axes.vlines`. Additionally, a horizontal line is plotted
+            at y=0 using `Axes.axhline`.
+
+            If ``False``, markers are plotted at the xcorr values using
+            `Axes.plot`.
+
+        maxlags : int, optional, default: 10
+            Number of lags to show. If None, will return all ``2 * len(x) - 1``
+            lags.
+
+        Returns
+        -------
+        lags : array (length ``2*maxlags+1``)
+            The lag vector.
+        c : array  (length ``2*maxlags+1``)
+            The auto correlation vector.
+        line : `.LineCollection` or `.Line2D`
+            `.Artist` added to the axes of the correlation:
+
+            - `.LineCollection` if *usevlines* is True.
+            - `.Line2D` if *usevlines* is False.
+        b : `.Line2D` or None
+            Horizontal line at 0 if *usevlines* is True
+            None *usevlines* is False.
+
+        Other Parameters
+        ----------------
+        linestyle : `.Line2D` property, optional
+            The linestyle for plotting the data points.
+            Only used if *usevlines* is ``False``.
+
+        marker : str, optional, default: 'o'
+            The marker for plotting the data points.
+            Only used if *usevlines* is ``False``.
+
+        Notes
+        -----
+        The cross correlation is performed with :func:`numpy.correlate` with
+        ``mode = "full"``.
+        """
+        Nx = len(x)
+        if Nx != len(y):
+            raise ValueError('x and y must be equal length')
+
+        x = detrend(np.asarray(x))
+        y = detrend(np.asarray(y))
+
+        correls = np.correlate(x, y, mode="full")
+
+        if normed:
+            correls /= np.sqrt(np.dot(x, x) * np.dot(y, y))
+
+        if maxlags is None:
+            maxlags = Nx - 1
+
+        if maxlags >= Nx or maxlags < 1:
+            raise ValueError('maxlags must be None or strictly '
+                             'positive < %d' % Nx)
+
+        lags = np.arange(-maxlags, maxlags + 1)
+        correls = correls[Nx - 1 - maxlags:Nx + maxlags]
+
+        if usevlines:
+            a = self.vlines(lags, [0], correls, **kwargs)
+            # Make label empty so only vertical lines get a legend entry
+            kwargs.pop('label', '')
+            b = self.axhline(**kwargs)
+        else:
+            kwargs.setdefault('marker', 'o')
+            kwargs.setdefault('linestyle', 'None')
+            a, = self.plot(lags, correls, **kwargs)
+            b = None
+        return lags, correls, a, b
+
+    #### Specialized plotting
+
+    # @_preprocess_data() # let 'plot' do the unpacking..
+    def step(self, x, y, *args, where='pre', data=None, **kwargs):
+        """
+        Make a step plot.
+
+        Call signatures::
+
+            step(x, y, [fmt], *, data=None, where='pre', **kwargs)
+            step(x, y, [fmt], x2, y2, [fmt2], ..., *, where='pre', **kwargs)
+
+        This is just a thin wrapper around `.plot` which changes some
+        formatting options. Most of the concepts and parameters of plot can be
+        used here as well.
+
+        Parameters
+        ----------
+        x : array-like
+            1-D sequence of x positions. It is assumed, but not checked, that
+            it is uniformly increasing.
+
+        y : array-like
+            1-D sequence of y levels.
+
+        fmt : str, optional
+            A format string, e.g. 'g' for a green line. See `.plot` for a more
+            detailed description.
+
+            Note: While full format strings are accepted, it is recommended to
+            only specify the color. Line styles are currently ignored (use
+            the keyword argument *linestyle* instead). Markers are accepted
+            and plotted on the given positions, however, this is a rarely
+            needed feature for step plots.
+
+        data : indexable object, optional
+            An object with labelled data. If given, provide the label names to
+            plot in *x* and *y*.
+
+        where : {'pre', 'post', 'mid'}, optional, default 'pre'
+            Define where the steps should be placed:
+
+            - 'pre': The y value is continued constantly to the left from
+              every *x* position, i.e. the interval ``(x[i-1], x[i]]`` has the
+              value ``y[i]``.
+            - 'post': The y value is continued constantly to the right from
+              every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
+              value ``y[i]``.
+            - 'mid': Steps occur half-way between the *x* positions.
+
+        Returns
+        -------
+        lines
+            A list of `.Line2D` objects representing the plotted data.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Additional parameters are the same as those for `.plot`.
+
+        Notes
+        -----
+        .. [notes section required to get data note injection right]
+        """
+        cbook._check_in_list(('pre', 'post', 'mid'), where=where)
+        kwargs['drawstyle'] = 'steps-' + where
+        return self.plot(x, y, *args, data=data, **kwargs)
+
+    @staticmethod
+    def _convert_dx(dx, x0, xconv, convert):
+        """
+        Small helper to do logic of width conversion flexibly.
+
+        *dx* and *x0* have units, but *xconv* has already been converted
+        to unitless (and is an ndarray).  This allows the *dx* to have units
+        that are different from *x0*, but are still accepted by the
+        ``__add__`` operator of *x0*.
+        """
+
+        # x should be an array...
+        assert type(xconv) is np.ndarray
+
+        if xconv.size == 0:
+            # xconv has already been converted, but maybe empty...
+            return convert(dx)
+
+        try:
+            # attempt to add the width to x0; this works for
+            # datetime+timedelta, for instance
+
+            # only use the first element of x and x0.  This saves
+            # having to be sure addition works across the whole
+            # vector.  This is particularly an issue if
+            # x0 and dx are lists so x0 + dx just concatenates the lists.
+            # We can't just cast x0 and dx to numpy arrays because that
+            # removes the units from unit packages like `pint` that
+            # wrap numpy arrays.
+            try:
+                x0 = cbook.safe_first_element(x0)
+            except (TypeError, IndexError, KeyError):
+                x0 = x0
+
+            try:
+                x = cbook.safe_first_element(xconv)
+            except (TypeError, IndexError, KeyError):
+                x = xconv
+
+            delist = False
+            if not np.iterable(dx):
+                dx = [dx]
+                delist = True
+            dx = [convert(x0 + ddx) - x for ddx in dx]
+            if delist:
+                dx = dx[0]
+        except (ValueError, TypeError, AttributeError):
+            # if the above fails (for any reason) just fallback to what
+            # we do by default and convert dx by itself.
+            dx = convert(dx)
+        return dx
+
+    @_preprocess_data()
+    @docstring.dedent_interpd
+    def bar(self, x, height, width=0.8, bottom=None, *, align="center",
+            **kwargs):
+        r"""
+        Make a bar plot.
+
+        The bars are positioned at *x* with the given *align*\ment. Their
+        dimensions are given by *width* and *height*. The vertical baseline
+        is *bottom* (default 0).
+
+        Each of *x*, *height*, *width*, and *bottom* may either be a scalar
+        applying to all bars, or it may be a sequence of length N providing a
+        separate value for each bar.
+
+        Parameters
+        ----------
+        x : sequence of scalars
+            The x coordinates of the bars. See also *align* for the
+            alignment of the bars to the coordinates.
+
+        height : scalar or sequence of scalars
+            The height(s) of the bars.
+
+        width : scalar or array-like, optional
+            The width(s) of the bars (default: 0.8).
+
+        bottom : scalar or array-like, optional
+            The y coordinate(s) of the bars bases (default: 0).
+
+        align : {'center', 'edge'}, optional, default: 'center'
+            Alignment of the bars to the *x* coordinates:
+
+            - 'center': Center the base on the *x* positions.
+            - 'edge': Align the left edges of the bars with the *x* positions.
+
+            To align the bars on the right edge pass a negative *width* and
+            ``align='edge'``.
+
+        Returns
+        -------
+        container : `.BarContainer`
+            Container with all the bars and optionally errorbars.
+
+        Other Parameters
+        ----------------
+        color : scalar or array-like, optional
+            The colors of the bar faces.
+
+        edgecolor : scalar or array-like, optional
+            The colors of the bar edges.
+
+        linewidth : scalar or array-like, optional
+            Width of the bar edge(s). If 0, don't draw edges.
+
+        tick_label : str or array-like, optional
+            The tick labels of the bars.
+            Default: None (Use default numeric labels.)
+
+        xerr, yerr : scalar or array-like of shape(N,) or shape(2, N), optional
+            If not *None*, add horizontal / vertical errorbars to the bar tips.
+            The values are +/- sizes relative to the data:
+
+            - scalar: symmetric +/- values for all bars
+            - shape(N,): symmetric +/- values for each bar
+            - shape(2, N): Separate - and + values for each bar. First row
+              contains the lower errors, the second row contains the upper
+              errors.
+            - *None*: No errorbar. (Default)
+
+            See :doc:`/gallery/statistics/errorbar_features`
+            for an example on the usage of ``xerr`` and ``yerr``.
+
+        ecolor : scalar or array-like, optional, default: 'black'
+            The line color of the errorbars.
+
+        capsize : scalar, optional
+           The length of the error bar caps in points.
+           Default: None, which will take the value from
+           :rc:`errorbar.capsize`.
+
+        error_kw : dict, optional
+            Dictionary of kwargs to be passed to the `~.Axes.errorbar`
+            method. Values of *ecolor* or *capsize* defined here take
+            precedence over the independent kwargs.
+
+        log : bool, optional, default: False
+            If *True*, set the y-axis to be log scale.
+
+        orientation : {'vertical',  'horizontal'}, optional
+            *This is for internal use only.* Please use `barh` for
+            horizontal bar plots. Default: 'vertical'.
+
+        See also
+        --------
+        barh: Plot a horizontal bar plot.
+
+        Notes
+        -----
+        The optional arguments *color*, *edgecolor*, *linewidth*,
+        *xerr*, and *yerr* can be either scalars or sequences of
+        length equal to the number of bars.  This enables you to use
+        bar as the basis for stacked bar charts, or candlestick plots.
+        Detail: *xerr* and *yerr* are passed directly to
+        :meth:`errorbar`, so they can also have shape 2xN for
+        independent specification of lower and upper errors.
+
+        Other optional kwargs:
+
+        %(Rectangle)s
+        """
+        kwargs = cbook.normalize_kwargs(kwargs, mpatches.Patch)
+        color = kwargs.pop('color', None)
+        if color is None:
+            color = self._get_patches_for_fill.get_next_color()
+        edgecolor = kwargs.pop('edgecolor', None)
+        linewidth = kwargs.pop('linewidth', None)
+
+        # Because xerr and yerr will be passed to errorbar, most dimension
+        # checking and processing will be left to the errorbar method.
+        xerr = kwargs.pop('xerr', None)
+        yerr = kwargs.pop('yerr', None)
+        error_kw = kwargs.pop('error_kw', {})
+        ezorder = error_kw.pop('zorder', None)
+        if ezorder is None:
+            ezorder = kwargs.get('zorder', None)
+            if ezorder is not None:
+                # If using the bar zorder, increment slightly to make sure
+                # errorbars are drawn on top of bars
+                ezorder += 0.01
+        error_kw.setdefault('zorder', ezorder)
+        ecolor = kwargs.pop('ecolor', 'k')
+        capsize = kwargs.pop('capsize', rcParams["errorbar.capsize"])
+        error_kw.setdefault('ecolor', ecolor)
+        error_kw.setdefault('capsize', capsize)
+
+        orientation = kwargs.pop('orientation', 'vertical')
+        cbook._check_in_list(['vertical', 'horizontal'],
+                             orientation=orientation)
+        log = kwargs.pop('log', False)
+        label = kwargs.pop('label', '')
+        tick_labels = kwargs.pop('tick_label', None)
+
+        y = bottom  # Matches barh call signature.
+        if orientation == 'vertical':
+            if y is None:
+                y = 0
+        elif orientation == 'horizontal':
+            if x is None:
+                x = 0
+
+        if orientation == 'vertical':
+            self._process_unit_info(xdata=x, ydata=height, kwargs=kwargs)
+            if log:
+                self.set_yscale('log', nonposy='clip')
+        elif orientation == 'horizontal':
+            self._process_unit_info(xdata=width, ydata=y, kwargs=kwargs)
+            if log:
+                self.set_xscale('log', nonposx='clip')
+
+        # lets do some conversions now since some types cannot be
+        # subtracted uniformly
+        if self.xaxis is not None:
+            x0 = x
+            x = np.asarray(self.convert_xunits(x))
+            width = self._convert_dx(width, x0, x, self.convert_xunits)
+            if xerr is not None:
+                xerr = self._convert_dx(xerr, x0, x, self.convert_xunits)
+        if self.yaxis is not None:
+            y0 = y
+            y = np.asarray(self.convert_yunits(y))
+            height = self._convert_dx(height, y0, y, self.convert_yunits)
+            if yerr is not None:
+                yerr = self._convert_dx(yerr, y0, y, self.convert_yunits)
+
+        x, height, width, y, linewidth = np.broadcast_arrays(
+            # Make args iterable too.
+            np.atleast_1d(x), height, width, y, linewidth)
+
+        # Now that units have been converted, set the tick locations.
+        if orientation == 'vertical':
+            tick_label_axis = self.xaxis
+            tick_label_position = x
+        elif orientation == 'horizontal':
+            tick_label_axis = self.yaxis
+            tick_label_position = y
+
+        linewidth = itertools.cycle(np.atleast_1d(linewidth))
+        color = itertools.chain(itertools.cycle(mcolors.to_rgba_array(color)),
+                                # Fallback if color == "none".
+                                itertools.repeat('none'))
+        if edgecolor is None:
+            edgecolor = itertools.repeat(None)
+        else:
+            edgecolor = itertools.chain(
+                itertools.cycle(mcolors.to_rgba_array(edgecolor)),
+                # Fallback if edgecolor == "none".
+                itertools.repeat('none'))
+
+        # We will now resolve the alignment and really have
+        # left, bottom, width, height vectors
+        cbook._check_in_list(['center', 'edge'], align=align)
+        if align == 'center':
+            if orientation == 'vertical':
+                try:
+                    left = x - width / 2
+                except TypeError as e:
+                    raise TypeError(f'the dtypes of parameters x ({x.dtype}) '
+                                    f'and width ({width.dtype}) '
+                                    f'are incompatible') from e
+                bottom = y
+            elif orientation == 'horizontal':
+                try:
+                    bottom = y - height / 2
+                except TypeError as e:
+                    raise TypeError(f'the dtypes of parameters y ({y.dtype}) '
+                                    f'and height ({height.dtype}) '
+                                    f'are incompatible') from e
+                left = x
+        elif align == 'edge':
+            left = x
+            bottom = y
+
+        patches = []
+        args = zip(left, bottom, width, height, color, edgecolor, linewidth)
+        for l, b, w, h, c, e, lw in args:
+            r = mpatches.Rectangle(
+                xy=(l, b), width=w, height=h,
+                facecolor=c,
+                edgecolor=e,
+                linewidth=lw,
+                label='_nolegend_',
+                )
+            r.update(kwargs)
+            r.get_path()._interpolation_steps = 100
+            if orientation == 'vertical':
+                r.sticky_edges.y.append(b)
+            elif orientation == 'horizontal':
+                r.sticky_edges.x.append(l)
+            self.add_patch(r)
+            patches.append(r)
+
+        if xerr is not None or yerr is not None:
+            if orientation == 'vertical':
+                # using list comps rather than arrays to preserve unit info
+                ex = [l + 0.5 * w for l, w in zip(left, width)]
+                ey = [b + h for b, h in zip(bottom, height)]
+
+            elif orientation == 'horizontal':
+                # using list comps rather than arrays to preserve unit info
+                ex = [l + w for l, w in zip(left, width)]
+                ey = [b + 0.5 * h for b, h in zip(bottom, height)]
+
+            error_kw.setdefault("label", '_nolegend_')
+
+            errorbar = self.errorbar(ex, ey,
+                                     yerr=yerr, xerr=xerr,
+                                     fmt='none', **error_kw)
+        else:
+            errorbar = None
+
+        self._request_autoscale_view()
+
+        bar_container = BarContainer(patches, errorbar, label=label)
+        self.add_container(bar_container)
+
+        if tick_labels is not None:
+            tick_labels = np.broadcast_to(tick_labels, len(patches))
+            tick_label_axis.set_ticks(tick_label_position)
+            tick_label_axis.set_ticklabels(tick_labels)
+
+        return bar_container
+
+    @docstring.dedent_interpd
+    def barh(self, y, width, height=0.8, left=None, *, align="center",
+             **kwargs):
+        r"""
+        Make a horizontal bar plot.
+
+        The bars are positioned at *y* with the given *align*\ment. Their
+        dimensions are given by *width* and *height*. The horizontal baseline
+        is *left* (default 0).
+
+        Each of *y*, *width*, *height*, and *left* may either be a scalar
+        applying to all bars, or it may be a sequence of length N providing a
+        separate value for each bar.
+
+        Parameters
+        ----------
+        y : scalar or array-like
+            The y coordinates of the bars. See also *align* for the
+            alignment of the bars to the coordinates.
+
+        width : scalar or array-like
+            The width(s) of the bars.
+
+        height : sequence of scalars, optional, default: 0.8
+            The heights of the bars.
+
+        left : sequence of scalars
+            The x coordinates of the left sides of the bars (default: 0).
+
+        align : {'center', 'edge'}, optional, default: 'center'
+            Alignment of the base to the *y* coordinates*:
+
+            - 'center': Center the bars on the *y* positions.
+            - 'edge': Align the bottom edges of the bars with the *y*
+              positions.
+
+            To align the bars on the top edge pass a negative *height* and
+            ``align='edge'``.
+
+        Returns
+        -------
+        container : `.BarContainer`
+            Container with all the bars and optionally errorbars.
+
+        Other Parameters
+        ----------------
+        color : scalar or array-like, optional
+            The colors of the bar faces.
+
+        edgecolor : scalar or array-like, optional
+            The colors of the bar edges.
+
+        linewidth : scalar or array-like, optional
+            Width of the bar edge(s). If 0, don't draw edges.
+
+        tick_label : str or array-like, optional
+            The tick labels of the bars.
+            Default: None (Use default numeric labels.)
+
+        xerr, yerr : scalar or array-like of shape(N,) or shape(2, N), optional
+            If not ``None``, add horizontal / vertical errorbars to the
+            bar tips. The values are +/- sizes relative to the data:
+
+            - scalar: symmetric +/- values for all bars
+            - shape(N,): symmetric +/- values for each bar
+            - shape(2, N): Separate - and + values for each bar. First row
+              contains the lower errors, the second row contains the upper
+              errors.
+            - *None*: No errorbar. (default)
+
+            See :doc:`/gallery/statistics/errorbar_features`
+            for an example on the usage of ``xerr`` and ``yerr``.
+
+        ecolor : scalar or array-like, optional, default: 'black'
+            The line color of the errorbars.
+
+        capsize : scalar, optional
+           The length of the error bar caps in points.
+           Default: None, which will take the value from
+           :rc:`errorbar.capsize`.
+
+        error_kw : dict, optional
+            Dictionary of kwargs to be passed to the `~.Axes.errorbar`
+            method. Values of *ecolor* or *capsize* defined here take
+            precedence over the independent kwargs.
+
+        log : bool, optional, default: False
+            If ``True``, set the x-axis to be log scale.
+
+        See also
+        --------
+        bar: Plot a vertical bar plot.
+
+        Notes
+        -----
+        The optional arguments *color*, *edgecolor*, *linewidth*,
+        *xerr*, and *yerr* can be either scalars or sequences of
+        length equal to the number of bars.  This enables you to use
+        bar as the basis for stacked bar charts, or candlestick plots.
+        Detail: *xerr* and *yerr* are passed directly to
+        :meth:`errorbar`, so they can also have shape 2xN for
+        independent specification of lower and upper errors.
+
+        Other optional kwargs:
+
+        %(Rectangle)s
+        """
+        kwargs.setdefault('orientation', 'horizontal')
+        patches = self.bar(x=left, height=height, width=width, bottom=y,
+                           align=align, **kwargs)
+        return patches
+
+    @_preprocess_data()
+    @docstring.dedent_interpd
+    def broken_barh(self, xranges, yrange, **kwargs):
+        """
+        Plot a horizontal sequence of rectangles.
+
+        A rectangle is drawn for each element of *xranges*. All rectangles
+        have the same vertical position and size defined by *yrange*.
+
+        This is a convenience function for instantiating a
+        `.BrokenBarHCollection`, adding it to the axes and autoscaling the
+        view.
+
+        Parameters
+        ----------
+        xranges : sequence of tuples (*xmin*, *xwidth*)
+            The x-positions and extends of the rectangles. For each tuple
+            (*xmin*, *xwidth*) a rectangle is drawn from *xmin* to *xmin* +
+            *xwidth*.
+        yrange : (*ymin*, *yheight*)
+            The y-position and extend for all the rectangles.
+
+        Other Parameters
+        ----------------
+        **kwargs : :class:`.BrokenBarHCollection` properties
+
+            Each *kwarg* can be either a single argument applying to all
+            rectangles, e.g.::
+
+                facecolors='black'
+
+            or a sequence of arguments over which is cycled, e.g.::
+
+                facecolors=('black', 'blue')
+
+            would create interleaving black and blue rectangles.
+
+            Supported keywords:
+
+            %(BrokenBarHCollection)s
+
+        Returns
+        -------
+        collection : A :class:`~.collections.BrokenBarHCollection`
+        """
+        # process the unit information
+        if len(xranges):
+            xdata = cbook.safe_first_element(xranges)
+        else:
+            xdata = None
+        if len(yrange):
+            ydata = cbook.safe_first_element(yrange)
+        else:
+            ydata = None
+        self._process_unit_info(xdata=xdata,
+                                ydata=ydata,
+                                kwargs=kwargs)
+        xranges_conv = []
+        for xr in xranges:
+            if len(xr) != 2:
+                raise ValueError('each range in xrange must be a sequence '
+                                 'with two elements (i.e. an Nx2 array)')
+            # convert the absolute values, not the x and dx...
+            x_conv = np.asarray(self.convert_xunits(xr[0]))
+            x1 = self._convert_dx(xr[1], xr[0], x_conv, self.convert_xunits)
+            xranges_conv.append((x_conv, x1))
+
+        yrange_conv = self.convert_yunits(yrange)
+
+        col = mcoll.BrokenBarHCollection(xranges_conv, yrange_conv, **kwargs)
+        self.add_collection(col, autolim=True)
+        self._request_autoscale_view()
+
+        return col
+
+    @_preprocess_data()
+    def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0,
+             label=None, use_line_collection=False):
+        """
+        Create a stem plot.
+
+        A stem plot plots vertical lines at each *x* location from the baseline
+        to *y*, and places a marker there.
+
+        Call signature::
+
+          stem([x,] y, linefmt=None, markerfmt=None, basefmt=None)
+
+        The x-positions are optional. The formats may be provided either as
+        positional or as keyword-arguments.
+
+        Parameters
+        ----------
+        x : array-like, optional
+            The x-positions of the stems. Default: (0, 1, ..., len(y) - 1).
+
+        y : array-like
+            The y-values of the stem heads.
+
+        linefmt : str, optional
+            A string defining the properties of the vertical lines. Usually,
+            this will be a color or a color and a linestyle:
+
+            =========  =============
+            Character  Line Style
+            =========  =============
+            ``'-'``    solid line
+            ``'--'``   dashed line
+            ``'-.'``   dash-dot line
+            ``':'``    dotted line
+            =========  =============
+
+            Default: 'C0-', i.e. solid line with the first color of the color
+            cycle.
+
+            Note: While it is technically possible to specify valid formats
+            other than color or color and linestyle (e.g. 'rx' or '-.'), this
+            is beyond the intention of the method and will most likely not
+            result in a reasonable reasonable plot.
+
+        markerfmt : str, optional
+            A string defining the properties of the markers at the stem heads.
+            Default: 'C0o', i.e. filled circles with the first color of the
+            color cycle.
+
+        basefmt : str, optional
+            A format string defining the properties of the baseline.
+
+            Default: 'C3-' ('C2-' in classic mode).
+
+        bottom : float, optional, default: 0
+            The y-position of the baseline.
+
+        label : str, optional, default: None
+            The label to use for the stems in legends.
+
+        use_line_collection : bool, optional, default: False
+            If ``True``, store and plot the stem lines as a
+            `~.collections.LineCollection` instead of individual lines. This
+            significantly increases performance, and will become the default
+            option in Matplotlib 3.3. If ``False``, defaults to the old
+            behavior of using a list of `.Line2D` objects.
+
+
+        Returns
+        -------
+        container : :class:`~matplotlib.container.StemContainer`
+            The container may be treated like a tuple
+            (*markerline*, *stemlines*, *baseline*)
+
+
+        Notes
+        -----
+        .. seealso::
+            The MATLAB function
+            `stem <http://www.mathworks.com/help/techdoc/ref/stem.html>`_
+            which inspired this method.
+
+        """
+        if not 1 <= len(args) <= 5:
+            raise TypeError('stem expected between 1 and 5 positional '
+                            'arguments, got {}'.format(args))
+
+        if len(args) == 1:
+            y, = args
+            x = np.arange(len(y))
+            args = ()
+        else:
+            x, y, *args = args
+
+        self._process_unit_info(xdata=x, ydata=y)
+        x = self.convert_xunits(x)
+        y = self.convert_yunits(y)
+
+        # defaults for formats
+        if linefmt is None:
+            try:
+                # fallback to positional argument
+                linefmt = args[0]
+            except IndexError:
+                linecolor = 'C0'
+                linemarker = 'None'
+                linestyle = '-'
+            else:
+                linestyle, linemarker, linecolor = \
+                    _process_plot_format(linefmt)
+        else:
+            linestyle, linemarker, linecolor = _process_plot_format(linefmt)
+
+        if markerfmt is None:
+            try:
+                # fallback to positional argument
+                markerfmt = args[1]
+            except IndexError:
+                markercolor = 'C0'
+                markermarker = 'o'
+                markerstyle = 'None'
+            else:
+                markerstyle, markermarker, markercolor = \
+                    _process_plot_format(markerfmt)
+        else:
+            markerstyle, markermarker, markercolor = \
+                _process_plot_format(markerfmt)
+
+        if basefmt is None:
+            try:
+                # fallback to positional argument
+                basefmt = args[2]
+            except IndexError:
+                if rcParams['_internal.classic_mode']:
+                    basecolor = 'C2'
+                else:
+                    basecolor = 'C3'
+                basemarker = 'None'
+                basestyle = '-'
+            else:
+                basestyle, basemarker, basecolor = \
+                    _process_plot_format(basefmt)
+        else:
+            basestyle, basemarker, basecolor = _process_plot_format(basefmt)
+
+        # New behaviour in 3.1 is to use a LineCollection for the stemlines
+        if use_line_collection:
+            stemlines = [((xi, bottom), (xi, yi)) for xi, yi in zip(x, y)]
+            if linestyle is None:
+                linestyle = rcParams['lines.linestyle']
+            stemlines = mcoll.LineCollection(stemlines, linestyles=linestyle,
+                                             colors=linecolor,
+                                             label='_nolegend_')
+            self.add_collection(stemlines)
+        # Old behaviour is to plot each of the lines individually
+        else:
+            cbook._warn_external(
+                'In Matplotlib 3.3 individual lines on a stem plot will be '
+                'added as a LineCollection instead of individual lines. '
+                'This significantly improves the performance of a stem plot. '
+                'To remove this warning and switch to the new behaviour, '
+                'set the "use_line_collection" keyword argument to True.')
+            stemlines = []
+            for xi, yi in zip(x, y):
+                l, = self.plot([xi, xi], [bottom, yi],
+                               color=linecolor, linestyle=linestyle,
+                               marker=linemarker, label="_nolegend_")
+                stemlines.append(l)
+
+        markerline, = self.plot(x, y, color=markercolor, linestyle=markerstyle,
+                                marker=markermarker, label="_nolegend_")
+
+        baseline, = self.plot([np.min(x), np.max(x)], [bottom, bottom],
+                              color=basecolor, linestyle=basestyle,
+                              marker=basemarker, label="_nolegend_")
+
+        stem_container = StemContainer((markerline, stemlines, baseline),
+                                       label=label)
+        self.add_container(stem_container)
+        return stem_container
+
+    @_preprocess_data(replace_names=["x", "explode", "labels", "colors"])
+    def pie(self, x, explode=None, labels=None, colors=None,
+            autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
+            startangle=None, radius=None, counterclock=True,
+            wedgeprops=None, textprops=None, center=(0, 0),
+            frame=False, rotatelabels=False):
+        """
+        Plot a pie chart.
+
+        Make a pie chart of array *x*.  The fractional area of each wedge is
+        given by ``x/sum(x)``.  If ``sum(x) < 1``, then the values of *x* give
+        the fractional area directly and the array will not be normalized. The
+        resulting pie will have an empty wedge of size ``1 - sum(x)``.
+
+        The wedges are plotted counterclockwise, by default starting from the
+        x-axis.
+
+        Parameters
+        ----------
+        x : array-like
+            The wedge sizes.
+
+        explode : array-like, optional, default: None
+            If not *None*, is a ``len(x)`` array which specifies the fraction
+            of the radius with which to offset each wedge.
+
+        labels : list, optional, default: None
+            A sequence of strings providing the labels for each wedge
+
+        colors : array-like, optional, default: None
+            A sequence of matplotlib color args through which the pie chart
+            will cycle.  If *None*, will use the colors in the currently
+            active cycle.
+
+        autopct : None (default), str, or function, optional
+            If not *None*, is a string or function used to label the wedges
+            with their numeric value.  The label will be placed inside the
+            wedge.  If it is a format string, the label will be ``fmt%pct``.
+            If it is a function, it will be called.
+
+        pctdistance : float, optional, default: 0.6
+            The ratio between the center of each pie slice and the start of
+            the text generated by *autopct*.  Ignored if *autopct* is *None*.
+
+        shadow : bool, optional, default: False
+            Draw a shadow beneath the pie.
+
+        labeldistance : float or None, optional, default: 1.1
+            The radial distance at which the pie labels are drawn.
+            If set to ``None``, label are not drawn, but are stored for use in
+            ``legend()``
+
+        startangle : float, optional, default: None
+            If not *None*, rotates the start of the pie chart by *angle*
+            degrees counterclockwise from the x-axis.
+
+        radius : float, optional, default: None
+            The radius of the pie, if *radius* is *None* it will be set to 1.
+
+        counterclock : bool, optional, default: True
+            Specify fractions direction, clockwise or counterclockwise.
+
+        wedgeprops : dict, optional, default: None
+            Dict of arguments passed to the wedge objects making the pie.
+            For example, you can pass in ``wedgeprops = {'linewidth': 3}``
+            to set the width of the wedge border lines equal to 3.
+            For more details, look at the doc/arguments of the wedge object.
+            By default ``clip_on=False``.
+
+        textprops : dict, optional, default: None
+            Dict of arguments to pass to the text objects.
+
+        center :  list of float, optional, default: (0, 0)
+            Center position of the chart. Takes value (0, 0) or is a sequence
+            of 2 scalars.
+
+        frame : bool, optional, default: False
+            Plot axes frame with the chart if true.
+
+        rotatelabels : bool, optional, default: False
+            Rotate each label to the angle of the corresponding slice if true.
+
+        Returns
+        -------
+        patches : list
+            A sequence of :class:`matplotlib.patches.Wedge` instances
+
+        texts : list
+            A list of the label :class:`matplotlib.text.Text` instances.
+
+        autotexts : list
+            A list of :class:`~matplotlib.text.Text` instances for the numeric
+            labels. This will only be returned if the parameter *autopct* is
+            not *None*.
+
+        Notes
+        -----
+        The pie chart will probably look best if the figure and axes are
+        square, or the Axes aspect is equal.
+        This method sets the aspect ratio of the axis to "equal".
+        The axes aspect ratio can be controlled with `Axes.set_aspect`.
+        """
+        self.set_aspect('equal')
+        # The use of float32 is "historical", but can't be changed without
+        # regenerating the test baselines.
+        x = np.asarray(x, np.float32)
+        if x.ndim != 1 and x.squeeze().ndim <= 1:
+            cbook.warn_deprecated(
+                "3.1", message="Non-1D inputs to pie() are currently "
+                "squeeze()d, but this behavior is deprecated since %(since)s "
+                "and will be removed %(removal)s; pass a 1D array instead.")
+            x = np.atleast_1d(x.squeeze())
+
+        sx = x.sum()
+        if sx > 1:
+            x = x / sx
+
+        if labels is None:
+            labels = [''] * len(x)
+        if explode is None:
+            explode = [0] * len(x)
+        if len(x) != len(labels):
+            raise ValueError("'label' must be of length 'x'")
+        if len(x) != len(explode):
+            raise ValueError("'explode' must be of length 'x'")
+        if colors is None:
+            get_next_color = self._get_patches_for_fill.get_next_color
+        else:
+            color_cycle = itertools.cycle(colors)
+
+            def get_next_color():
+                return next(color_cycle)
+
+        if radius is None:
+            radius = 1
+
+        # Starting theta1 is the start fraction of the circle
+        if startangle is None:
+            theta1 = 0
+        else:
+            theta1 = startangle / 360.0
+
+        # set default values in wedge_prop
+        if wedgeprops is None:
+            wedgeprops = {}
+        wedgeprops.setdefault('clip_on', False)
+
+        if textprops is None:
+            textprops = {}
+        textprops.setdefault('clip_on', False)
+
+        texts = []
+        slices = []
+        autotexts = []
+
+        for frac, label, expl in zip(x, labels, explode):
+            x, y = center
+            theta2 = (theta1 + frac) if counterclock else (theta1 - frac)
+            thetam = 2 * np.pi * 0.5 * (theta1 + theta2)
+            x += expl * math.cos(thetam)
+            y += expl * math.sin(thetam)
+
+            w = mpatches.Wedge((x, y), radius, 360. * min(theta1, theta2),
+                               360. * max(theta1, theta2),
+                               facecolor=get_next_color(),
+                               **wedgeprops)
+            slices.append(w)
+            self.add_patch(w)
+            w.set_label(label)
+
+            if shadow:
+                # make sure to add a shadow after the call to
+                # add_patch so the figure and transform props will be
+                # set
+                shad = mpatches.Shadow(w, -0.02, -0.02)
+                shad.set_zorder(0.9 * w.get_zorder())
+                shad.set_label('_nolegend_')
+                self.add_patch(shad)
+
+            if labeldistance is not None:
+                xt = x + labeldistance * radius * math.cos(thetam)
+                yt = y + labeldistance * radius * math.sin(thetam)
+                label_alignment_h = 'left' if xt > 0 else 'right'
+                label_alignment_v = 'center'
+                label_rotation = 'horizontal'
+                if rotatelabels:
+                    label_alignment_v = 'bottom' if yt > 0 else 'top'
+                    label_rotation = (np.rad2deg(thetam)
+                                      + (0 if xt > 0 else 180))
+                props = dict(horizontalalignment=label_alignment_h,
+                             verticalalignment=label_alignment_v,
+                             rotation=label_rotation,
+                             size=rcParams['xtick.labelsize'])
+                props.update(textprops)
+
+                t = self.text(xt, yt, label, **props)
+
+                texts.append(t)
+
+            if autopct is not None:
+                xt = x + pctdistance * radius * math.cos(thetam)
+                yt = y + pctdistance * radius * math.sin(thetam)
+                if isinstance(autopct, str):
+                    s = autopct % (100. * frac)
+                elif callable(autopct):
+                    s = autopct(100. * frac)
+                else:
+                    raise TypeError(
+                        'autopct must be callable or a format string')
+
+                props = dict(horizontalalignment='center',
+                             verticalalignment='center')
+                props.update(textprops)
+                t = self.text(xt, yt, s, **props)
+
+                autotexts.append(t)
+
+            theta1 = theta2
+
+        if not frame:
+            self.set_frame_on(False)
+
+            self.set_xlim((-1.25 + center[0],
+                           1.25 + center[0]))
+            self.set_ylim((-1.25 + center[1],
+                           1.25 + center[1]))
+            self.set_xticks([])
+            self.set_yticks([])
+
+        if autopct is None:
+            return slices, texts
+        else:
+            return slices, texts, autotexts
+
+    @_preprocess_data(replace_names=["x", "y", "xerr", "yerr"],
+                      label_namer="y")
+    @docstring.dedent_interpd
+    def errorbar(self, x, y, yerr=None, xerr=None,
+                 fmt='', ecolor=None, elinewidth=None, capsize=None,
+                 barsabove=False, lolims=False, uplims=False,
+                 xlolims=False, xuplims=False, errorevery=1, capthick=None,
+                 **kwargs):
+        """
+        Plot y versus x as lines and/or markers with attached errorbars.
+
+        *x*, *y* define the data locations, *xerr*, *yerr* define the errorbar
+        sizes. By default, this draws the data markers/lines as well the
+        errorbars. Use fmt='none' to draw errorbars without any data markers.
+
+        Parameters
+        ----------
+        x, y : scalar or array-like
+            The data positions.
+
+        xerr, yerr : scalar or array-like, shape(N,) or shape(2, N), optional
+            The errorbar sizes:
+
+            - scalar: Symmetric +/- values for all data points.
+            - shape(N,): Symmetric +/-values for each data point.
+            - shape(2, N): Separate - and + values for each bar. First row
+              contains the lower errors, the second row contains the upper
+              errors.
+            - *None*: No errorbar.
+
+            Note that all error arrays should have *positive* values.
+
+            See :doc:`/gallery/statistics/errorbar_features`
+            for an example on the usage of ``xerr`` and ``yerr``.
+
+        fmt : str, optional, default: ''
+            The format for the data points / data lines. See `.plot` for
+            details.
+
+            Use 'none' (case insensitive) to plot errorbars without any data
+            markers.
+
+        ecolor : color, optional, default: None
+            The color of the errorbar lines.  If None, use the color of the
+            line connecting the markers.
+
+        elinewidth : scalar, optional, default: None
+            The linewidth of the errorbar lines. If None, the linewidth of
+            the current style is used.
+
+        capsize : scalar, optional, default: None
+            The length of the error bar caps in points. If None, it will take
+            the value from :rc:`errorbar.capsize`.
+
+        capthick : scalar, optional, default: None
+            An alias to the keyword argument *markeredgewidth* (a.k.a. *mew*).
+            This setting is a more sensible name for the property that
+            controls the thickness of the error bar cap in points. For
+            backwards compatibility, if *mew* or *markeredgewidth* are given,
+            then they will over-ride *capthick*. This may change in future
+            releases.
+
+        barsabove : bool, optional, default: False
+            If True, will plot the errorbars above the plot
+            symbols. Default is below.
+
+        lolims, uplims, xlolims, xuplims : bool, optional, default: False
+            These arguments can be used to indicate that a value gives only
+            upper/lower limits. In that case a caret symbol is used to
+            indicate this. *lims*-arguments may be of the same type as *xerr*
+            and *yerr*.  To use limits with inverted axes, :meth:`set_xlim`
+            or :meth:`set_ylim` must be called before :meth:`errorbar`.
+
+        errorevery : int or (int, int), optional, default: 1
+            draws error bars on a subset of the data. *errorevery* =N draws
+            error bars on the points (x[::N], y[::N]).
+            *errorevery* =(start, N) draws error bars on the points
+            (x[start::N], y[start::N]). e.g. errorevery=(6, 3)
+            adds error bars to the data at (x[6], x[9], x[12], x[15], ...).
+            Used to avoid overlapping error bars when two series share x-axis
+            values.
+
+        Returns
+        -------
+        container : :class:`~.container.ErrorbarContainer`
+            The container contains:
+
+            - plotline: `.Line2D` instance of x, y plot markers and/or line.
+            - caplines: A tuple of `.Line2D` instances of the error bar caps.
+            - barlinecols: A tuple of
+              :class:`~matplotlib.collections.LineCollection` with the
+              horizontal and vertical error ranges.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            All other keyword arguments are passed on to the plot
+            command for the markers. For example, this code makes big red
+            squares with thick green edges::
+
+                x, y, yerr = rand(3, 10)
+                errorbar(x, y, yerr, marker='s', mfc='red',
+                         mec='green', ms=20, mew=4)
+
+            where *mfc*, *mec*, *ms* and *mew* are aliases for the longer
+            property names, *markerfacecolor*, *markeredgecolor*, *markersize*
+            and *markeredgewidth*.
+
+            Valid kwargs for the marker properties are `.Lines2D` properties:
+
+            %(_Line2D_docstr)s
+        """
+        kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
+        # anything that comes in as 'None', drop so the default thing
+        # happens down stream
+        kwargs = {k: v for k, v in kwargs.items() if v is not None}
+        kwargs.setdefault('zorder', 2)
+
+        try:
+            offset, errorevery = errorevery
+        except TypeError:
+            offset = 0
+
+        if errorevery < 1 or int(errorevery) != errorevery:
+            raise ValueError(
+                'errorevery must be positive integer or tuple of integers')
+        if int(offset) != offset:
+            raise ValueError("errorevery's starting index must be an integer")
+
+        self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs)
+
+        plot_line = (fmt.lower() != 'none')
+        label = kwargs.pop("label", None)
+
+        if fmt == '':
+            fmt_style_kwargs = {}
+        else:
+            fmt_style_kwargs = {k: v for k, v in
+                                zip(('linestyle', 'marker', 'color'),
+                                    _process_plot_format(fmt))
+                                if v is not None}
+        if fmt == 'none':
+            # Remove alpha=0 color that _process_plot_format returns
+            fmt_style_kwargs.pop('color')
+
+        if ('color' in kwargs or 'color' in fmt_style_kwargs or
+                ecolor is not None):
+            base_style = {}
+            if 'color' in kwargs:
+                base_style['color'] = kwargs.pop('color')
+        else:
+            base_style = next(self._get_lines.prop_cycler)
+
+        base_style['label'] = '_nolegend_'
+        base_style.update(fmt_style_kwargs)
+        if 'color' not in base_style:
+            base_style['color'] = 'C0'
+        if ecolor is None:
+            ecolor = base_style['color']
+        # make sure all the args are iterable; use lists not arrays to
+        # preserve units
+        if not np.iterable(x):
+            x = [x]
+
+        if not np.iterable(y):
+            y = [y]
+
+        if xerr is not None:
+            if not np.iterable(xerr):
+                xerr = [xerr] * len(x)
+
+        if yerr is not None:
+            if not np.iterable(yerr):
+                yerr = [yerr] * len(y)
+
+        # make the style dict for the 'normal' plot line
+        plot_line_style = {
+            **base_style,
+            **kwargs,
+            'zorder': (kwargs['zorder'] - .1 if barsabove else
+                       kwargs['zorder'] + .1),
+        }
+
+        # make the style dict for the line collections (the bars)
+        eb_lines_style = dict(base_style)
+        eb_lines_style.pop('marker', None)
+        eb_lines_style.pop('linestyle', None)
+        eb_lines_style['color'] = ecolor
+
+        if elinewidth:
+            eb_lines_style['linewidth'] = elinewidth
+        elif 'linewidth' in kwargs:
+            eb_lines_style['linewidth'] = kwargs['linewidth']
+
+        for key in ('transform', 'alpha', 'zorder', 'rasterized'):
+            if key in kwargs:
+                eb_lines_style[key] = kwargs[key]
+
+        # set up cap style dictionary
+        eb_cap_style = dict(base_style)
+        # eject any marker information from format string
+        eb_cap_style.pop('marker', None)
+        eb_lines_style.pop('markerfacecolor', None)
+        eb_lines_style.pop('markeredgewidth', None)
+        eb_lines_style.pop('markeredgecolor', None)
+        eb_cap_style.pop('ls', None)
+        eb_cap_style['linestyle'] = 'none'
+        if capsize is None:
+            capsize = rcParams["errorbar.capsize"]
+        if capsize > 0:
+            eb_cap_style['markersize'] = 2. * capsize
+        if capthick is not None:
+            eb_cap_style['markeredgewidth'] = capthick
+
+        # For backwards-compat, allow explicit setting of
+        # 'markeredgewidth' to over-ride capthick.
+        for key in ('markeredgewidth', 'transform', 'alpha',
+                    'zorder', 'rasterized'):
+            if key in kwargs:
+                eb_cap_style[key] = kwargs[key]
+        eb_cap_style['color'] = ecolor
+
+        data_line = None
+        if plot_line:
+            data_line = mlines.Line2D(x, y, **plot_line_style)
+            self.add_line(data_line)
+
+        barcols = []
+        caplines = []
+
+        # arrays fine here, they are booleans and hence not units
+        lolims = np.broadcast_to(lolims, len(x)).astype(bool)
+        uplims = np.broadcast_to(uplims, len(x)).astype(bool)
+        xlolims = np.broadcast_to(xlolims, len(x)).astype(bool)
+        xuplims = np.broadcast_to(xuplims, len(x)).astype(bool)
+
+        everymask = np.zeros(len(x), bool)
+        everymask[offset::errorevery] = True
+
+        def xywhere(xs, ys, mask):
+            """
+            return xs[mask], ys[mask] where mask is True but xs and
+            ys are not arrays
+            """
+            assert len(xs) == len(ys)
+            assert len(xs) == len(mask)
+            xs = [thisx for thisx, b in zip(xs, mask) if b]
+            ys = [thisy for thisy, b in zip(ys, mask) if b]
+            return xs, ys
+
+        def extract_err(err, data):
+            """
+            Private function to parse *err* and subtract/add it to *data*.
+
+            Both *err* and *data* are already iterables at this point.
+            """
+            try:  # Asymmetric error: pair of 1D iterables.
+                a, b = err
+                iter(a)
+                iter(b)
+            except (TypeError, ValueError):
+                a = b = err  # Symmetric error: 1D iterable.
+            # This could just be `np.ndim(a) > 1 and np.ndim(b) > 1`, except
+            # for the (undocumented, but tested) support for (n, 1) arrays.
+            a_sh = np.shape(a)
+            b_sh = np.shape(b)
+            if (len(a_sh) > 2 or (len(a_sh) == 2 and a_sh[1] != 1)
+                    or len(b_sh) > 2 or (len(b_sh) == 2 and b_sh[1] != 1)):
+                raise ValueError(
+                    "err must be a scalar or a 1D or (2, n) array-like")
+            if len(a_sh) == 2 or len(b_sh) == 2:
+                cbook.warn_deprecated(
+                    "3.1", message="Support for passing a (n, 1)-shaped error "
+                    "array to errorbar() is deprecated since Matplotlib "
+                    "%(since)s and will be removed %(removal)s; pass a 1D "
+                    "array instead.")
+            # Using list comprehensions rather than arrays to preserve units.
+            for e in [a, b]:
+                if len(data) != len(e):
+                    raise ValueError(
+                        f"The lengths of the data ({len(data)}) and the "
+                        f"error {len(e)} do not match")
+            low = [v - e for v, e in zip(data, a)]
+            high = [v + e for v, e in zip(data, b)]
+            return low, high
+
+        if xerr is not None:
+            left, right = extract_err(xerr, x)
+            # select points without upper/lower limits in x and
+            # draw normal errorbars for these points
+            noxlims = ~(xlolims | xuplims)
+            if noxlims.any() or len(noxlims) == 0:
+                yo, _ = xywhere(y, right, noxlims & everymask)
+                lo, ro = xywhere(left, right, noxlims & everymask)
+                barcols.append(self.hlines(yo, lo, ro, **eb_lines_style))
+                if capsize > 0:
+                    caplines.append(mlines.Line2D(lo, yo, marker='|',
+                                                  **eb_cap_style))
+                    caplines.append(mlines.Line2D(ro, yo, marker='|',
+                                                  **eb_cap_style))
+
+            if xlolims.any():
+                yo, _ = xywhere(y, right, xlolims & everymask)
+                lo, ro = xywhere(x, right, xlolims & everymask)
+                barcols.append(self.hlines(yo, lo, ro, **eb_lines_style))
+                rightup, yup = xywhere(right, y, xlolims & everymask)
+                if self.xaxis_inverted():
+                    marker = mlines.CARETLEFTBASE
+                else:
+                    marker = mlines.CARETRIGHTBASE
+                caplines.append(
+                    mlines.Line2D(rightup, yup, ls='None', marker=marker,
+                                  **eb_cap_style))
+                if capsize > 0:
+                    xlo, ylo = xywhere(x, y, xlolims & everymask)
+                    caplines.append(mlines.Line2D(xlo, ylo, marker='|',
+                                                  **eb_cap_style))
+
+            if xuplims.any():
+                yo, _ = xywhere(y, right, xuplims & everymask)
+                lo, ro = xywhere(left, x, xuplims & everymask)
+                barcols.append(self.hlines(yo, lo, ro, **eb_lines_style))
+                leftlo, ylo = xywhere(left, y, xuplims & everymask)
+                if self.xaxis_inverted():
+                    marker = mlines.CARETRIGHTBASE
+                else:
+                    marker = mlines.CARETLEFTBASE
+                caplines.append(
+                    mlines.Line2D(leftlo, ylo, ls='None', marker=marker,
+                                  **eb_cap_style))
+                if capsize > 0:
+                    xup, yup = xywhere(x, y, xuplims & everymask)
+                    caplines.append(mlines.Line2D(xup, yup, marker='|',
+                                                  **eb_cap_style))
+
+        if yerr is not None:
+            lower, upper = extract_err(yerr, y)
+            # select points without upper/lower limits in y and
+            # draw normal errorbars for these points
+            noylims = ~(lolims | uplims)
+            if noylims.any() or len(noylims) == 0:
+                xo, _ = xywhere(x, lower, noylims & everymask)
+                lo, uo = xywhere(lower, upper, noylims & everymask)
+                barcols.append(self.vlines(xo, lo, uo, **eb_lines_style))
+                if capsize > 0:
+                    caplines.append(mlines.Line2D(xo, lo, marker='_',
+                                                  **eb_cap_style))
+                    caplines.append(mlines.Line2D(xo, uo, marker='_',
+                                                  **eb_cap_style))
+
+            if lolims.any():
+                xo, _ = xywhere(x, lower, lolims & everymask)
+                lo, uo = xywhere(y, upper, lolims & everymask)
+                barcols.append(self.vlines(xo, lo, uo, **eb_lines_style))
+                xup, upperup = xywhere(x, upper, lolims & everymask)
+                if self.yaxis_inverted():
+                    marker = mlines.CARETDOWNBASE
+                else:
+                    marker = mlines.CARETUPBASE
+                caplines.append(
+                    mlines.Line2D(xup, upperup, ls='None', marker=marker,
+                                  **eb_cap_style))
+                if capsize > 0:
+                    xlo, ylo = xywhere(x, y, lolims & everymask)
+                    caplines.append(mlines.Line2D(xlo, ylo, marker='_',
+                                                  **eb_cap_style))
+
+            if uplims.any():
+                xo, _ = xywhere(x, lower, uplims & everymask)
+                lo, uo = xywhere(lower, y, uplims & everymask)
+                barcols.append(self.vlines(xo, lo, uo, **eb_lines_style))
+                xlo, lowerlo = xywhere(x, lower, uplims & everymask)
+                if self.yaxis_inverted():
+                    marker = mlines.CARETUPBASE
+                else:
+                    marker = mlines.CARETDOWNBASE
+                caplines.append(
+                    mlines.Line2D(xlo, lowerlo, ls='None', marker=marker,
+                                  **eb_cap_style))
+                if capsize > 0:
+                    xup, yup = xywhere(x, y, uplims & everymask)
+                    caplines.append(mlines.Line2D(xup, yup, marker='_',
+                                                  **eb_cap_style))
+        for l in caplines:
+            self.add_line(l)
+
+        self._request_autoscale_view()
+        errorbar_container = ErrorbarContainer((data_line, tuple(caplines),
+                                                tuple(barcols)),
+                                               has_xerr=(xerr is not None),
+                                               has_yerr=(yerr is not None),
+                                               label=label)
+        self.containers.append(errorbar_container)
+
+        return errorbar_container  # (l0, caplines, barcols)
+
+    @cbook._rename_parameter("3.1", "manage_xticks", "manage_ticks")
+    @_preprocess_data()
+    def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
+                positions=None, widths=None, patch_artist=None,
+                bootstrap=None, usermedians=None, conf_intervals=None,
+                meanline=None, showmeans=None, showcaps=None,
+                showbox=None, showfliers=None, boxprops=None,
+                labels=None, flierprops=None, medianprops=None,
+                meanprops=None, capprops=None, whiskerprops=None,
+                manage_ticks=True, autorange=False, zorder=None):
+        """
+        Make a box and whisker plot.
+
+        Make a box and whisker plot for each column of ``x`` or each
+        vector in sequence ``x``.  The box extends from the lower to
+        upper quartile values of the data, with a line at the median.
+        The whiskers extend from the box to show the range of the
+        data.  Flier points are those past the end of the whiskers.
+
+        Parameters
+        ----------
+        x : Array or a sequence of vectors.
+            The input data.
+
+        notch : bool, optional (False)
+            If `True`, will produce a notched box plot. Otherwise, a
+            rectangular boxplot is produced. The notches represent the
+            confidence interval (CI) around the median. See the entry
+            for the ``bootstrap`` parameter for information regarding
+            how the locations of the notches are computed.
+
+            .. note::
+
+                In cases where the values of the CI are less than the
+                lower quartile or greater than the upper quartile, the
+                notches will extend beyond the box, giving it a
+                distinctive "flipped" appearance. This is expected
+                behavior and consistent with other statistical
+                visualization packages.
+
+        sym : str, optional
+            The default symbol for flier points. Enter an empty string
+            ('') if you don't want to show fliers. If `None`, then the
+            fliers default to 'b+'  If you want more control use the
+            flierprops kwarg.
+
+        vert : bool, optional (True)
+            If `True` (default), makes the boxes vertical. If `False`,
+            everything is drawn horizontally.
+
+        whis : float or (float, float) (default = 1.5)
+            The position of the whiskers.
+
+            If a float, the lower whisker is at the lowest datum above
+            ``Q1 - whis*(Q3-Q1)``, and the upper whisker at the highest datum
+            below ``Q3 + whis*(Q3-Q1)``, where Q1 and Q3 are the first and
+            third quartiles.  The default value of ``whis = 1.5`` corresponds
+            to Tukey's original definition of boxplots.
+
+            If a pair of floats, they indicate the percentiles at which to
+            draw the whiskers (e.g., (5, 95)).  In particular, setting this to
+            (0, 100) results in whiskers covering the whole range of the data.
+            "range" is a deprecated synonym for (0, 100).
+
+            In the edge case where ``Q1 == Q3``, *whis* is automatically set
+            to (0, 100) (cover the whole range of the data) if *autorange* is
+            True.
+
+            Beyond the whiskers, data are considered outliers and are plotted
+            as individual points.
+
+        bootstrap : int, optional
+            Specifies whether to bootstrap the confidence intervals
+            around the median for notched boxplots. If ``bootstrap`` is
+            None, no bootstrapping is performed, and notches are
+            calculated using a Gaussian-based asymptotic approximation
+            (see McGill, R., Tukey, J.W., and Larsen, W.A., 1978, and
+            Kendall and Stuart, 1967). Otherwise, bootstrap specifies
+            the number of times to bootstrap the median to determine its
+            95% confidence intervals. Values between 1000 and 10000 are
+            recommended.
+
+        usermedians : array-like, optional
+            An array or sequence whose first dimension (or length) is
+            compatible with ``x``. This overrides the medians computed
+            by matplotlib for each element of ``usermedians`` that is not
+            `None`. When an element of ``usermedians`` is None, the median
+            will be computed by matplotlib as normal.
+
+        conf_intervals : array-like, optional
+            Array or sequence whose first dimension (or length) is
+            compatible with ``x`` and whose second dimension is 2. When
+            the an element of ``conf_intervals`` is not None, the
+            notch locations computed by matplotlib are overridden
+            (provided ``notch`` is `True`). When an element of
+            ``conf_intervals`` is `None`, the notches are computed by the
+            method specified by the other kwargs (e.g., ``bootstrap``).
+
+        positions : array-like, optional
+            Sets the positions of the boxes. The ticks and limits are
+            automatically set to match the positions. Defaults to
+            `range(1, N+1)` where N is the number of boxes to be drawn.
+
+        widths : scalar or array-like
+            Sets the width of each box either with a scalar or a
+            sequence. The default is 0.5, or ``0.15*(distance between
+            extreme positions)``, if that is smaller.
+
+        patch_artist : bool, optional (False)
+            If `False` produces boxes with the Line2D artist. Otherwise,
+            boxes and drawn with Patch artists.
+
+        labels : sequence, optional
+            Labels for each dataset. Length must be compatible with
+            dimensions of ``x``.
+
+        manage_ticks : bool, optional (True)
+            If True, the tick locations and labels will be adjusted to match
+            the boxplot positions.
+
+        autorange : bool, optional (False)
+            When `True` and the data are distributed such that the 25th and
+            75th percentiles are equal, ``whis`` is set to (0, 100) such
+            that the whisker ends are at the minimum and maximum of the data.
+
+        meanline : bool, optional (False)
+            If `True` (and ``showmeans`` is `True`), will try to render
+            the mean as a line spanning the full width of the box
+            according to ``meanprops`` (see below). Not recommended if
+            ``shownotches`` is also True. Otherwise, means will be shown
+            as points.
+
+        zorder : scalar, optional (None)
+            Sets the zorder of the boxplot.
+
+        Other Parameters
+        ----------------
+        showcaps : bool, optional (True)
+            Show the caps on the ends of whiskers.
+        showbox : bool, optional (True)
+            Show the central box.
+        showfliers : bool, optional (True)
+            Show the outliers beyond the caps.
+        showmeans : bool, optional (False)
+            Show the arithmetic means.
+        capprops : dict, optional (None)
+            Specifies the style of the caps.
+        boxprops : dict, optional (None)
+            Specifies the style of the box.
+        whiskerprops : dict, optional (None)
+            Specifies the style of the whiskers.
+        flierprops : dict, optional (None)
+            Specifies the style of the fliers.
+        medianprops : dict, optional (None)
+            Specifies the style of the median.
+        meanprops : dict, optional (None)
+            Specifies the style of the mean.
+
+        Returns
+        -------
+        result : dict
+          A dictionary mapping each component of the boxplot to a list
+          of the `.Line2D` instances created. That dictionary has the
+          following keys (assuming vertical boxplots):
+
+          - ``boxes``: the main body of the boxplot showing the
+            quartiles and the median's confidence intervals if
+            enabled.
+
+          - ``medians``: horizontal lines at the median of each box.
+
+          - ``whiskers``: the vertical lines extending to the most
+            extreme, non-outlier data points.
+
+          - ``caps``: the horizontal lines at the ends of the
+            whiskers.
+
+          - ``fliers``: points representing data that extend beyond
+            the whiskers (fliers).
+
+          - ``means``: points or lines representing the means.
+
+        """
+
+        # Missing arguments default to rcParams.
+        if whis is None:
+            whis = rcParams['boxplot.whiskers']
+        if bootstrap is None:
+            bootstrap = rcParams['boxplot.bootstrap']
+
+        bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap,
+                                       labels=labels, autorange=autorange)
+        if notch is None:
+            notch = rcParams['boxplot.notch']
+        if vert is None:
+            vert = rcParams['boxplot.vertical']
+        if patch_artist is None:
+            patch_artist = rcParams['boxplot.patchartist']
+        if meanline is None:
+            meanline = rcParams['boxplot.meanline']
+        if showmeans is None:
+            showmeans = rcParams['boxplot.showmeans']
+        if showcaps is None:
+            showcaps = rcParams['boxplot.showcaps']
+        if showbox is None:
+            showbox = rcParams['boxplot.showbox']
+        if showfliers is None:
+            showfliers = rcParams['boxplot.showfliers']
+
+        if boxprops is None:
+            boxprops = {}
+        if whiskerprops is None:
+            whiskerprops = {}
+        if capprops is None:
+            capprops = {}
+        if medianprops is None:
+            medianprops = {}
+        if meanprops is None:
+            meanprops = {}
+        if flierprops is None:
+            flierprops = {}
+
+        if patch_artist:
+            boxprops['linestyle'] = 'solid'  # Not consistent with bxp.
+            if 'color' in boxprops:
+                boxprops['edgecolor'] = boxprops.pop('color')
+
+        # if non-default sym value, put it into the flier dictionary
+        # the logic for providing the default symbol ('b+') now lives
+        # in bxp in the initial value of final_flierprops
+        # handle all of the *sym* related logic here so we only have to pass
+        # on the flierprops dict.
+        if sym is not None:
+            # no-flier case, which should really be done with
+            # 'showfliers=False' but none-the-less deal with it to keep back
+            # compatibility
+            if sym == '':
+                # blow away existing dict and make one for invisible markers
+                flierprops = dict(linestyle='none', marker='', color='none')
+                # turn the fliers off just to be safe
+                showfliers = False
+            # now process the symbol string
+            else:
+                # process the symbol string
+                # discarded linestyle
+                _, marker, color = _process_plot_format(sym)
+                # if we have a marker, use it
+                if marker is not None:
+                    flierprops['marker'] = marker
+                # if we have a color, use it
+                if color is not None:
+                    # assume that if color is passed in the user want
+                    # filled symbol, if the users want more control use
+                    # flierprops
+                    flierprops['color'] = color
+                    flierprops['markerfacecolor'] = color
+                    flierprops['markeredgecolor'] = color
+
+        # replace medians if necessary:
+        if usermedians is not None:
+            if (len(np.ravel(usermedians)) != len(bxpstats) or
+                    np.shape(usermedians)[0] != len(bxpstats)):
+                raise ValueError('usermedians length not compatible with x')
+            else:
+                # reassign medians as necessary
+                for stats, med in zip(bxpstats, usermedians):
+                    if med is not None:
+                        stats['med'] = med
+
+        if conf_intervals is not None:
+            if np.shape(conf_intervals)[0] != len(bxpstats):
+                err_mess = 'conf_intervals length not compatible with x'
+                raise ValueError(err_mess)
+            else:
+                for stats, ci in zip(bxpstats, conf_intervals):
+                    if ci is not None:
+                        if len(ci) != 2:
+                            raise ValueError('each confidence interval must '
+                                             'have two values')
+                        else:
+                            if ci[0] is not None:
+                                stats['cilo'] = ci[0]
+                            if ci[1] is not None:
+                                stats['cihi'] = ci[1]
+
+        artists = self.bxp(bxpstats, positions=positions, widths=widths,
+                           vert=vert, patch_artist=patch_artist,
+                           shownotches=notch, showmeans=showmeans,
+                           showcaps=showcaps, showbox=showbox,
+                           boxprops=boxprops, flierprops=flierprops,
+                           medianprops=medianprops, meanprops=meanprops,
+                           meanline=meanline, showfliers=showfliers,
+                           capprops=capprops, whiskerprops=whiskerprops,
+                           manage_ticks=manage_ticks, zorder=zorder)
+        return artists
+
+    @cbook._rename_parameter("3.1", "manage_xticks", "manage_ticks")
+    def bxp(self, bxpstats, positions=None, widths=None, vert=True,
+            patch_artist=False, shownotches=False, showmeans=False,
+            showcaps=True, showbox=True, showfliers=True,
+            boxprops=None, whiskerprops=None, flierprops=None,
+            medianprops=None, capprops=None, meanprops=None,
+            meanline=False, manage_ticks=True, zorder=None):
+        """
+        Drawing function for box and whisker plots.
+
+        Make a box and whisker plot for each column of *x* or each
+        vector in sequence *x*.  The box extends from the lower to
+        upper quartile values of the data, with a line at the median.
+        The whiskers extend from the box to show the range of the
+        data.  Flier points are those past the end of the whiskers.
+
+        Parameters
+        ----------
+        bxpstats : list of dicts
+          A list of dictionaries containing stats for each boxplot.
+          Required keys are:
+
+          - ``med``: The median (scalar float).
+
+          - ``q1``: The first quartile (25th percentile) (scalar
+            float).
+
+          - ``q3``: The third quartile (75th percentile) (scalar
+            float).
+
+          - ``whislo``: Lower bound of the lower whisker (scalar
+            float).
+
+          - ``whishi``: Upper bound of the upper whisker (scalar
+            float).
+
+          Optional keys are:
+
+          - ``mean``: The mean (scalar float). Needed if
+            ``showmeans=True``.
+
+          - ``fliers``: Data beyond the whiskers (sequence of floats).
+            Needed if ``showfliers=True``.
+
+          - ``cilo`` & ``cihi``: Lower and upper confidence intervals
+            about the median. Needed if ``shownotches=True``.
+
+          - ``label``: Name of the dataset (string). If available,
+            this will be used a tick label for the boxplot
+
+        positions : array-like, default = [1, 2, ..., n]
+          Sets the positions of the boxes. The ticks and limits
+          are automatically set to match the positions.
+
+        widths : array-like, default = None
+          Either a scalar or a vector and sets the width of each
+          box. The default is ``0.15*(distance between extreme
+          positions)``, clipped to no less than 0.15 and no more than
+          0.5.
+
+        vert : bool, default = True
+          If `True` (default), makes the boxes vertical.  If `False`,
+          makes horizontal boxes.
+
+        patch_artist : bool, default = False
+          If `False` produces boxes with the `.Line2D` artist.
+          If `True` produces boxes with the `~matplotlib.patches.Patch` artist.
+
+        shownotches : bool, default = False
+          If `False` (default), produces a rectangular box plot.
+          If `True`, will produce a notched box plot
+
+        showmeans : bool, default = False
+          If `True`, will toggle on the rendering of the means
+
+        showcaps  : bool, default = True
+          If `True`, will toggle on the rendering of the caps
+
+        showbox  : bool, default = True
+          If `True`, will toggle on the rendering of the box
+
+        showfliers : bool, default = True
+          If `True`, will toggle on the rendering of the fliers
+
+        boxprops : dict or None (default)
+          If provided, will set the plotting style of the boxes
+
+        whiskerprops : dict or None (default)
+          If provided, will set the plotting style of the whiskers
+
+        capprops : dict or None (default)
+          If provided, will set the plotting style of the caps
+
+        flierprops : dict or None (default)
+          If provided will set the plotting style of the fliers
+
+        medianprops : dict or None (default)
+          If provided, will set the plotting style of the medians
+
+        meanprops : dict or None (default)
+          If provided, will set the plotting style of the means
+
+        meanline : bool, default = False
+          If `True` (and *showmeans* is `True`), will try to render the mean
+          as a line spanning the full width of the box according to
+          *meanprops*. Not recommended if *shownotches* is also True.
+          Otherwise, means will be shown as points.
+
+        manage_ticks : bool, default = True
+          If True, the tick locations and labels will be adjusted to match the
+          boxplot positions.
+
+        zorder : scalar, default = None
+          The zorder of the resulting boxplot.
+
+        Returns
+        -------
+        result : dict
+          A dictionary mapping each component of the boxplot to a list
+          of the `.Line2D` instances created. That dictionary has the
+          following keys (assuming vertical boxplots):
+
+          - ``boxes``: the main body of the boxplot showing the
+            quartiles and the median's confidence intervals if
+            enabled.
+
+          - ``medians``: horizontal lines at the median of each box.
+
+          - ``whiskers``: the vertical lines extending to the most
+            extreme, non-outlier data points.
+
+          - ``caps``: the horizontal lines at the ends of the
+            whiskers.
+
+          - ``fliers``: points representing data that extend beyond
+            the whiskers (fliers).
+
+          - ``means``: points or lines representing the means.
+
+        Examples
+        --------
+        .. plot:: gallery/statistics/bxp.py
+
+        """
+        # lists of artists to be output
+        whiskers = []
+        caps = []
+        boxes = []
+        medians = []
+        means = []
+        fliers = []
+
+        # empty list of xticklabels
+        datalabels = []
+
+        # Use default zorder if none specified
+        if zorder is None:
+            zorder = mlines.Line2D.zorder
+
+        zdelta = 0.1
+
+        def line_props_with_rcdefaults(subkey, explicit, zdelta=0):
+            d = {k.split('.')[-1]: v for k, v in rcParams.items()
+                 if k.startswith(f'boxplot.{subkey}')}
+            d['zorder'] = zorder + zdelta
+            if explicit is not None:
+                d.update(
+                    cbook.normalize_kwargs(explicit, mlines.Line2D._alias_map))
+            return d
+
+        # box properties
+        if patch_artist:
+            final_boxprops = dict(
+                linestyle=rcParams['boxplot.boxprops.linestyle'],
+                linewidth=rcParams['boxplot.boxprops.linewidth'],
+                edgecolor=rcParams['boxplot.boxprops.color'],
+                facecolor=('white' if rcParams['_internal.classic_mode'] else
+                           rcParams['patch.facecolor']),
+                zorder=zorder,
+            )
+            if boxprops is not None:
+                final_boxprops.update(
+                    cbook.normalize_kwargs(
+                        boxprops, mpatches.PathPatch._alias_map))
+        else:
+            final_boxprops = line_props_with_rcdefaults('boxprops', boxprops)
+        final_whiskerprops = line_props_with_rcdefaults(
+            'whiskerprops', whiskerprops)
+        final_capprops = line_props_with_rcdefaults(
+            'capprops', capprops)
+        final_flierprops = line_props_with_rcdefaults(
+            'flierprops', flierprops)
+        final_medianprops = line_props_with_rcdefaults(
+            'medianprops', medianprops, zdelta)
+        final_meanprops = line_props_with_rcdefaults(
+            'meanprops', meanprops, zdelta)
+        removed_prop = 'marker' if meanline else 'linestyle'
+        # Only remove the property if it's not set explicitly as a parameter.
+        if meanprops is None or removed_prop not in meanprops:
+            final_meanprops[removed_prop] = ''
+
+        def to_vc(xs, ys):
+            # convert arguments to verts and codes, append (0, 0) (ignored).
+            verts = np.append(np.column_stack([xs, ys]), [(0, 0)], 0)
+            codes = ([mpath.Path.MOVETO]
+                     + [mpath.Path.LINETO] * (len(verts) - 2)
+                     + [mpath.Path.CLOSEPOLY])
+            return verts, codes
+
+        def patch_list(xs, ys, **kwargs):
+            verts, codes = to_vc(xs, ys)
+            path = mpath.Path(verts, codes)
+            patch = mpatches.PathPatch(path, **kwargs)
+            self.add_artist(patch)
+            return [patch]
+
+        # vertical or horizontal plot?
+        if vert:
+            def doplot(*args, **kwargs):
+                return self.plot(*args, **kwargs)
+
+            def dopatch(xs, ys, **kwargs):
+                return patch_list(xs, ys, **kwargs)
+
+        else:
+            def doplot(*args, **kwargs):
+                shuffled = []
+                for i in range(0, len(args), 2):
+                    shuffled.extend([args[i + 1], args[i]])
+                return self.plot(*shuffled, **kwargs)
+
+            def dopatch(xs, ys, **kwargs):
+                xs, ys = ys, xs  # flip X, Y
+                return patch_list(xs, ys, **kwargs)
+
+        # input validation
+        N = len(bxpstats)
+        datashape_message = ("List of boxplot statistics and `{0}` "
+                             "values must have same the length")
+        # check position
+        if positions is None:
+            positions = list(range(1, N + 1))
+        elif len(positions) != N:
+            raise ValueError(datashape_message.format("positions"))
+
+        positions = np.array(positions)
+        if len(positions) > 0 and not isinstance(positions[0], Number):
+            raise TypeError("positions should be an iterable of numbers")
+
+        # width
+        if widths is None:
+            widths = [np.clip(0.15 * np.ptp(positions), 0.15, 0.5)] * N
+        elif np.isscalar(widths):
+            widths = [widths] * N
+        elif len(widths) != N:
+            raise ValueError(datashape_message.format("widths"))
+
+        for pos, width, stats in zip(positions, widths, bxpstats):
+            # try to find a new label
+            datalabels.append(stats.get('label', pos))
+
+            # whisker coords
+            whisker_x = np.ones(2) * pos
+            whiskerlo_y = np.array([stats['q1'], stats['whislo']])
+            whiskerhi_y = np.array([stats['q3'], stats['whishi']])
+
+            # cap coords
+            cap_left = pos - width * 0.25
+            cap_right = pos + width * 0.25
+            cap_x = np.array([cap_left, cap_right])
+            cap_lo = np.ones(2) * stats['whislo']
+            cap_hi = np.ones(2) * stats['whishi']
+
+            # box and median coords
+            box_left = pos - width * 0.5
+            box_right = pos + width * 0.5
+            med_y = [stats['med'], stats['med']]
+
+            # notched boxes
+            if shownotches:
+                box_x = [box_left, box_right, box_right, cap_right, box_right,
+                         box_right, box_left, box_left, cap_left, box_left,
+                         box_left]
+                box_y = [stats['q1'], stats['q1'], stats['cilo'],
+                         stats['med'], stats['cihi'], stats['q3'],
+                         stats['q3'], stats['cihi'], stats['med'],
+                         stats['cilo'], stats['q1']]
+                med_x = cap_x
+
+            # plain boxes
+            else:
+                box_x = [box_left, box_right, box_right, box_left, box_left]
+                box_y = [stats['q1'], stats['q1'], stats['q3'], stats['q3'],
+                         stats['q1']]
+                med_x = [box_left, box_right]
+
+            # maybe draw the box:
+            if showbox:
+                if patch_artist:
+                    boxes.extend(dopatch(box_x, box_y, **final_boxprops))
+                else:
+                    boxes.extend(doplot(box_x, box_y, **final_boxprops))
+
+            # draw the whiskers
+            whiskers.extend(doplot(
+                whisker_x, whiskerlo_y, **final_whiskerprops
+            ))
+            whiskers.extend(doplot(
+                whisker_x, whiskerhi_y, **final_whiskerprops
+            ))
+
+            # maybe draw the caps:
+            if showcaps:
+                caps.extend(doplot(cap_x, cap_lo, **final_capprops))
+                caps.extend(doplot(cap_x, cap_hi, **final_capprops))
+
+            # draw the medians
+            medians.extend(doplot(med_x, med_y, **final_medianprops))
+
+            # maybe draw the means
+            if showmeans:
+                if meanline:
+                    means.extend(doplot(
+                        [box_left, box_right], [stats['mean'], stats['mean']],
+                        **final_meanprops
+                    ))
+                else:
+                    means.extend(doplot(
+                        [pos], [stats['mean']], **final_meanprops
+                    ))
+
+            # maybe draw the fliers
+            if showfliers:
+                # fliers coords
+                flier_x = np.full(len(stats['fliers']), pos, dtype=np.float64)
+                flier_y = stats['fliers']
+
+                fliers.extend(doplot(
+                    flier_x, flier_y, **final_flierprops
+                ))
+
+        if manage_ticks:
+            axis_name = "x" if vert else "y"
+            interval = getattr(self.dataLim, f"interval{axis_name}")
+            axis = getattr(self, f"{axis_name}axis")
+            positions = axis.convert_units(positions)
+            # The 0.5 additional padding ensures reasonable-looking boxes
+            # even when drawing a single box.  We set the sticky edge to
+            # prevent margins expansion, in order to match old behavior (back
+            # when separate calls to boxplot() would completely reset the axis
+            # limits regardless of what was drawn before).  The sticky edges
+            # are attached to the median lines, as they are always present.
+            interval[:] = (min(interval[0], min(positions) - .5),
+                           max(interval[1], max(positions) + .5))
+            for median, position in zip(medians, positions):
+                getattr(median.sticky_edges, axis_name).extend(
+                    [position - .5, position + .5])
+            # Modified from Axis.set_ticks and Axis.set_ticklabels.
+            locator = axis.get_major_locator()
+            if not isinstance(axis.get_major_locator(),
+                              mticker.FixedLocator):
+                locator = mticker.FixedLocator([])
+                axis.set_major_locator(locator)
+            locator.locs = np.array([*locator.locs, *positions])
+            formatter = axis.get_major_formatter()
+            if not isinstance(axis.get_major_formatter(),
+                              mticker.FixedFormatter):
+                formatter = mticker.FixedFormatter([])
+                axis.set_major_formatter(formatter)
+            formatter.seq = [*formatter.seq, *datalabels]
+
+            self._request_autoscale_view(
+                scalex=self._autoscaleXon, scaley=self._autoscaleYon)
+
+        return dict(whiskers=whiskers, caps=caps, boxes=boxes,
+                    medians=medians, fliers=fliers, means=means)
+
+    @staticmethod
+    def _parse_scatter_color_args(c, edgecolors, kwargs, xsize,
+                                  get_next_color_func):
+        """
+        Helper function to process color related arguments of `.Axes.scatter`.
+
+        Argument precedence for facecolors:
+
+        - c (if not None)
+        - kwargs['facecolors']
+        - kwargs['facecolor']
+        - kwargs['color'] (==kwcolor)
+        - 'b' if in classic mode else the result of ``get_next_color_func()``
+
+        Argument precedence for edgecolors:
+
+        - edgecolors (is an explicit kw argument in scatter())
+        - kwargs['edgecolor']
+        - kwargs['color'] (==kwcolor)
+        - 'face' if not in classic mode else None
+
+        Parameters
+        ----------
+        c : color or sequence or sequence of color or None
+            See argument description of `.Axes.scatter`.
+        edgecolors : color or sequence of color or {'face', 'none'} or None
+            See argument description of `.Axes.scatter`.
+        kwargs : dict
+            Additional kwargs. If these keys exist, we pop and process them:
+            'facecolors', 'facecolor', 'edgecolor', 'color'
+            Note: The dict is modified by this function.
+        xsize : int
+            The size of the x and y arrays passed to `.Axes.scatter`.
+        get_next_color_func : callable
+            A callable that returns a color. This color is used as facecolor
+            if no other color is provided.
+
+            Note, that this is a function rather than a fixed color value to
+            support conditional evaluation of the next color.  As of the
+            current implementation obtaining the next color from the
+            property cycle advances the cycle. This must only happen if we
+            actually use the color, which will only be decided within this
+            method.
+
+        Returns
+        -------
+        c
+            The input *c* if it was not *None*, else a color derived from the
+            other inputs or defaults.
+        colors : array(N, 4) or None
+            The facecolors as RGBA values, or *None* if a colormap is used.
+        edgecolors
+            The edgecolor.
+
+        """
+        facecolors = kwargs.pop('facecolors', None)
+        facecolors = kwargs.pop('facecolor', facecolors)
+        edgecolors = kwargs.pop('edgecolor', edgecolors)
+
+        kwcolor = kwargs.pop('color', None)
+
+        if kwcolor is not None and c is not None:
+            raise ValueError("Supply a 'c' argument or a 'color'"
+                             " kwarg but not both; they differ but"
+                             " their functionalities overlap.")
+
+        if kwcolor is not None:
+            try:
+                mcolors.to_rgba_array(kwcolor)
+            except ValueError:
+                raise ValueError(
+                    "'color' kwarg must be an color or sequence of color "
+                    "specs.  For a sequence of values to be color-mapped, use "
+                    "the 'c' argument instead.")
+            if edgecolors is None:
+                edgecolors = kwcolor
+            if facecolors is None:
+                facecolors = kwcolor
+
+        if edgecolors is None and not rcParams['_internal.classic_mode']:
+            edgecolors = rcParams['scatter.edgecolors']
+
+        c_was_none = c is None
+        if c is None:
+            c = (facecolors if facecolors is not None
+                 else "b" if rcParams['_internal.classic_mode']
+                 else get_next_color_func())
+        c_is_string_or_strings = (
+            isinstance(c, str)
+            or (isinstance(c, collections.abc.Iterable) and len(c) > 0
+                and isinstance(cbook.safe_first_element(c), str)))
+
+        def invalid_shape_exception(csize, xsize):
+            return ValueError(
+                f"'c' argument has {csize} elements, which is inconsistent "
+                f"with 'x' and 'y' with size {xsize}.")
+
+        c_is_mapped = False  # Unless proven otherwise below.
+        valid_shape = True  # Unless proven otherwise below.
+        if not c_was_none and kwcolor is None and not c_is_string_or_strings:
+            try:  # First, does 'c' look suitable for value-mapping?
+                c = np.asanyarray(c, dtype=float)
+            except ValueError:
+                pass  # Failed to convert to float array; must be color specs.
+            else:
+                # If c can be either mapped values or a RGB(A) color, prefer
+                # the former if shapes match, the latter otherwise.
+                if c.size == xsize:
+                    c = c.ravel()
+                    c_is_mapped = True
+                else:  # Wrong size; it must not be intended for mapping.
+                    if c.shape in ((3,), (4,)):
+                        _log.warning(
+                            "'c' argument looks like a single numeric RGB or "
+                            "RGBA sequence, which should be avoided as value-"
+                            "mapping will have precedence in case its length "
+                            "matches with 'x' & 'y'.  Please use a 2-D array "
+                            "with a single row if you really want to specify "
+                            "the same RGB or RGBA value for all points.")
+                    valid_shape = False
+        if not c_is_mapped:
+            try:  # Is 'c' acceptable as PathCollection facecolors?
+                colors = mcolors.to_rgba_array(c)
+            except ValueError:
+                if not valid_shape:
+                    raise invalid_shape_exception(c.size, xsize)
+                # Both the mapping *and* the RGBA conversion failed: pretty
+                # severe failure => one may appreciate a verbose feedback.
+                raise ValueError(
+                    f"'c' argument must be a color, a sequence of colors, or "
+                    f"a sequence of numbers, not {c}")
+            else:
+                if len(colors) not in (0, 1, xsize):
+                    # NB: remember that a single color is also acceptable.
+                    # Besides *colors* will be an empty array if c == 'none'.
+                    raise invalid_shape_exception(len(colors), xsize)
+        else:
+            colors = None  # use cmap, norm after collection is created
+        return c, colors, edgecolors
+
+    @_preprocess_data(replace_names=["x", "y", "s", "linewidths",
+                                     "edgecolors", "c", "facecolor",
+                                     "facecolors", "color"],
+                      label_namer="y")
+    @cbook._delete_parameter("3.2", "verts")
+    def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
+                vmin=None, vmax=None, alpha=None, linewidths=None,
+                verts=None, edgecolors=None, *, plotnonfinite=False,
+                **kwargs):
+        """
+        A scatter plot of *y* vs. *x* with varying marker size and/or color.
+
+        Parameters
+        ----------
+        x, y : scalar or array-like, shape (n, )
+            The data positions.
+
+        s : scalar or array-like, shape (n, ), optional
+            The marker size in points**2.
+            Default is ``rcParams['lines.markersize'] ** 2``.
+
+        c : color, sequence, or sequence of colors, optional
+            The marker color. Possible values:
+
+            - A single color format string.
+            - A sequence of colors of length n.
+            - A scalar or sequence of n numbers to be mapped to colors using
+              *cmap* and *norm*.
+            - A 2-D array in which the rows are RGB or RGBA.
+
+            Note that *c* should not be a single numeric RGB or RGBA sequence
+            because that is indistinguishable from an array of values to be
+            colormapped. If you want to specify the same RGB or RGBA value for
+            all points, use a 2-D array with a single row.  Otherwise, value-
+            matching will have precedence in case of a size matching with *x*
+            and *y*.
+
+            Defaults to ``None``. In that case the marker color is determined
+            by the value of ``color``, ``facecolor`` or ``facecolors``. In case
+            those are not specified or ``None``, the marker color is determined
+            by the next color of the ``Axes``' current "shape and fill" color
+            cycle. This cycle defaults to :rc:`axes.prop_cycle`.
+
+        marker : `~matplotlib.markers.MarkerStyle`, optional
+            The marker style. *marker* can be either an instance of the class
+            or the text shorthand for a particular marker.
+            Defaults to ``None``, in which case it takes the value of
+            :rc:`scatter.marker` = 'o'.
+            See `~matplotlib.markers` for more information about marker styles.
+
+        cmap : `~matplotlib.colors.Colormap`, optional, default: None
+            A `.Colormap` instance or registered colormap name. *cmap* is only
+            used if *c* is an array of floats. If ``None``, defaults to rc
+            ``image.cmap``.
+
+        norm : `~matplotlib.colors.Normalize`, optional, default: None
+            A `.Normalize` instance is used to scale luminance data to 0, 1.
+            *norm* is only used if *c* is an array of floats. If *None*, use
+            the default `.colors.Normalize`.
+
+        vmin, vmax : scalar, optional, default: None
+            *vmin* and *vmax* are used in conjunction with *norm* to normalize
+            luminance data. If None, the respective min and max of the color
+            array is used. *vmin* and *vmax* are ignored if you pass a *norm*
+            instance.
+
+        alpha : scalar, optional, default: None
+            The alpha blending value, between 0 (transparent) and 1 (opaque).
+
+        linewidths : scalar or array-like, optional, default: None
+            The linewidth of the marker edges. Note: The default *edgecolors*
+            is 'face'. You may want to change this as well.
+            If *None*, defaults to :rc:`lines.linewidth`.
+
+        edgecolors : {'face', 'none', *None*} or color or sequence of color, \
+optional.
+            The edge color of the marker. Possible values:
+
+            - 'face': The edge color will always be the same as the face color.
+            - 'none': No patch boundary will be drawn.
+            - A Matplotlib color or sequence of color.
+
+            Defaults to ``None``, in which case it takes the value of
+            :rc:`scatter.edgecolors` = 'face'.
+
+            For non-filled markers, the *edgecolors* kwarg is ignored and
+            forced to 'face' internally.
+
+        plotnonfinite : boolean, optional, default: False
+            Set to plot points with nonfinite *c*, in conjunction with
+            `~matplotlib.colors.Colormap.set_bad`.
+
+        Returns
+        -------
+        paths : `~matplotlib.collections.PathCollection`
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.collections.Collection` properties
+
+        See Also
+        --------
+        plot : To plot scatter plots when markers are identical in size and
+            color.
+
+        Notes
+        -----
+        * The `.plot` function will be faster for scatterplots where markers
+          don't vary in size or color.
+
+        * Any or all of *x*, *y*, *s*, and *c* may be masked arrays, in which
+          case all masks will be combined and only unmasked points will be
+          plotted.
+
+        * Fundamentally, scatter works with 1-D arrays; *x*, *y*, *s*, and *c*
+          may be input as N-D arrays, but within scatter they will be
+          flattened. The exception is *c*, which will be flattened only if its
+          size matches the size of *x* and *y*.
+
+        """
+        # Process **kwargs to handle aliases, conflicts with explicit kwargs:
+
+        self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs)
+        x = self.convert_xunits(x)
+        y = self.convert_yunits(y)
+
+        # np.ma.ravel yields an ndarray, not a masked array,
+        # unless its argument is a masked array.
+        x = np.ma.ravel(x)
+        y = np.ma.ravel(y)
+        if x.size != y.size:
+            raise ValueError("x and y must be the same size")
+
+        if s is None:
+            s = (20 if rcParams['_internal.classic_mode'] else
+                 rcParams['lines.markersize'] ** 2.0)
+        s = np.ma.ravel(s)
+        if len(s) not in (1, x.size):
+            raise ValueError("s must be a scalar, or the same size as x and y")
+
+        c, colors, edgecolors = \
+            self._parse_scatter_color_args(
+                c, edgecolors, kwargs, x.size,
+                get_next_color_func=self._get_patches_for_fill.get_next_color)
+
+        if plotnonfinite and colors is None:
+            c = np.ma.masked_invalid(c)
+            x, y, s, edgecolors, linewidths = \
+                cbook._combine_masks(x, y, s, edgecolors, linewidths)
+        else:
+            x, y, s, c, colors, edgecolors, linewidths = \
+                cbook._combine_masks(
+                    x, y, s, c, colors, edgecolors, linewidths)
+
+        scales = s   # Renamed for readability below.
+
+        # load default marker from rcParams
+        if marker is None:
+            marker = rcParams['scatter.marker']
+
+        if isinstance(marker, mmarkers.MarkerStyle):
+            marker_obj = marker
+        else:
+            marker_obj = mmarkers.MarkerStyle(marker)
+
+        path = marker_obj.get_path().transformed(
+            marker_obj.get_transform())
+        if not marker_obj.is_filled():
+            edgecolors = 'face'
+            linewidths = rcParams['lines.linewidth']
+
+        offsets = np.ma.column_stack([x, y])
+
+        collection = mcoll.PathCollection(
+                (path,), scales,
+                facecolors=colors,
+                edgecolors=edgecolors,
+                linewidths=linewidths,
+                offsets=offsets,
+                transOffset=kwargs.pop('transform', self.transData),
+                alpha=alpha
+                )
+        collection.set_transform(mtransforms.IdentityTransform())
+        collection.update(kwargs)
+
+        if colors is None:
+            collection.set_array(c)
+            collection.set_cmap(cmap)
+            collection.set_norm(norm)
+
+            if vmin is not None or vmax is not None:
+                collection.set_clim(vmin, vmax)
+            else:
+                collection.autoscale_None()
+
+        # Classic mode only:
+        # ensure there are margins to allow for the
+        # finite size of the symbols.  In v2.x, margins
+        # are present by default, so we disable this
+        # scatter-specific override.
+        if rcParams['_internal.classic_mode']:
+            if self._xmargin < 0.05 and x.size > 0:
+                self.set_xmargin(0.05)
+            if self._ymargin < 0.05 and x.size > 0:
+                self.set_ymargin(0.05)
+
+        self.add_collection(collection)
+        self._request_autoscale_view()
+
+        return collection
+
+    @_preprocess_data(replace_names=["x", "y"], label_namer="y")
+    @docstring.dedent_interpd
+    def hexbin(self, x, y, C=None, gridsize=100, bins=None,
+               xscale='linear', yscale='linear', extent=None,
+               cmap=None, norm=None, vmin=None, vmax=None,
+               alpha=None, linewidths=None, edgecolors='face',
+               reduce_C_function=np.mean, mincnt=None, marginals=False,
+               **kwargs):
+        """
+        Make a 2D hexagonal binning plot of points *x*, *y*.
+
+        If *C* is *None*, the value of the hexagon is determined by the number
+        of points in the hexagon. Otherwise, *C* specifies values at the
+        coordinate (x[i], y[i]). For each hexagon, these values are reduced
+        using *reduce_C_function*.
+
+        Parameters
+        ----------
+        x, y : array-like
+            The data positions. *x* and *y* must be of the same length.
+
+        C : array-like, optional
+            If given, these values are accumulated in the bins. Otherwise,
+            every point has a value of 1. Must be of the same length as *x*
+            and *y*.
+
+        gridsize : int or (int, int), default: 100
+            If a single int, the number of hexagons in the *x*-direction.
+            The number of hexagons in the *y*-direction is chosen such that
+            the hexagons are approximately regular.
+
+            Alternatively, if a tuple (*nx*, *ny*), the number of hexagons
+            in the *x*-direction and the *y*-direction.
+
+        bins : 'log' or int or sequence, default: *None*
+            Discretization of the hexagon values.
+
+            - If *None*, no binning is applied; the color of each hexagon
+              directly corresponds to its count value.
+            - If 'log', use a logarithmic scale for the color map.
+              Internally, :math:`log_{10}(i+1)` is used to determine the
+              hexagon color. This is equivalent to ``norm=LogNorm()``.
+            - If an integer, divide the counts in the specified number
+              of bins, and color the hexagons accordingly.
+            - If a sequence of values, the values of the lower bound of
+              the bins to be used.
+
+        xscale : {'linear', 'log'}, default: 'linear'
+            Use a linear or log10 scale on the horizontal axis.
+
+        yscale : {'linear', 'log'}, default: 'linear'
+            Use a linear or log10 scale on the vertical axis.
+
+        mincnt : int > 0, default: *None*
+            If not *None*, only display cells with more than *mincnt*
+            number of points in the cell.
+
+        marginals : bool, default: *False*
+            If marginals is *True*, plot the marginal density as
+            colormapped rectangles along the bottom of the x-axis and
+            left of the y-axis.
+
+        extent : float, default: *None*
+            The limits of the bins. The default assigns the limits
+            based on *gridsize*, *x*, *y*, *xscale* and *yscale*.
+
+            If *xscale* or *yscale* is set to 'log', the limits are
+            expected to be the exponent for a power of 10. E.g. for
+            x-limits of 1 and 50 in 'linear' scale and y-limits
+            of 10 and 1000 in 'log' scale, enter (1, 50, 1, 3).
+
+            Order of scalars is (left, right, bottom, top).
+
+        Other Parameters
+        ----------------
+        cmap : str or `~matplotlib.colors.Colormap`, optional
+            The Colormap instance or registered colormap name used to map
+            the bin values to colors. Defaults to :rc:`image.cmap`.
+
+        norm : `~matplotlib.colors.Normalize`, optional
+            The Normalize instance scales the bin values to the canonical
+            colormap range [0, 1] for mapping to colors. By default, the data
+            range is mapped to the colorbar range using linear scaling.
+
+        vmin, vmax : float, optional, default: None
+            The colorbar range. If *None*, suitable min/max values are
+            automatically chosen by the `~.Normalize` instance (defaults to
+            the respective min/max values of the bins in case of the default
+            linear scaling). This is ignored if *norm* is given.
+
+        alpha : float between 0 and 1, optional
+            The alpha blending value, between 0 (transparent) and 1 (opaque).
+
+        linewidths : float, default: *None*
+            If *None*, defaults to 1.0.
+
+        edgecolors : {'face', 'none', *None*} or color, default: 'face'
+            The color of the hexagon edges. Possible values are:
+
+            - 'face': Draw the edges in the same color as the fill color.
+            - 'none': No edges are drawn. This can sometimes lead to unsightly
+              unpainted pixels between the hexagons.
+            - *None*: Draw outlines in the default color.
+            - An explicit matplotlib color.
+
+        reduce_C_function : callable, default is `numpy.mean`
+            The function to aggregate *C* within the bins. It is ignored if
+            *C* is not given. This must have the signature::
+
+                def reduce_C_function(C: array) -> float
+
+            Commonly used functions are:
+
+            - `numpy.mean`: average of the points
+            - `numpy.sum`: integral of the point values
+            - `numpy.max`: value taken from the largest point
+
+        **kwargs : `~matplotlib.collections.PolyCollection` properties
+            All other keyword arguments are passed on to `.PolyCollection`:
+
+            %(PolyCollection)s
+
+        Returns
+        -------
+        polycollection : `~matplotlib.collections.PolyCollection`
+            A `.PolyCollection` defining the hexagonal bins.
+
+            - `.PolyCollection.get_offset` contains a Mx2 array containing
+              the x, y positions of the M hexagon centers.
+            - `.PolyCollection.get_array` contains the values of the M
+              hexagons.
+
+            If *marginals* is *True*, horizontal
+            bar and vertical bar (both PolyCollections) will be attached
+            to the return collection as attributes *hbar* and *vbar*.
+
+        """
+        self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs)
+
+        x, y, C = cbook.delete_masked_points(x, y, C)
+
+        # Set the size of the hexagon grid
+        if np.iterable(gridsize):
+            nx, ny = gridsize
+        else:
+            nx = gridsize
+            ny = int(nx / math.sqrt(3))
+        # Count the number of data in each hexagon
+        x = np.array(x, float)
+        y = np.array(y, float)
+        if xscale == 'log':
+            if np.any(x <= 0.0):
+                raise ValueError("x contains non-positive values, so can not"
+                                 " be log-scaled")
+            x = np.log10(x)
+        if yscale == 'log':
+            if np.any(y <= 0.0):
+                raise ValueError("y contains non-positive values, so can not"
+                                 " be log-scaled")
+            y = np.log10(y)
+        if extent is not None:
+            xmin, xmax, ymin, ymax = extent
+        else:
+            xmin, xmax = (np.min(x), np.max(x)) if len(x) else (0, 1)
+            ymin, ymax = (np.min(y), np.max(y)) if len(y) else (0, 1)
+
+            # to avoid issues with singular data, expand the min/max pairs
+            xmin, xmax = mtransforms.nonsingular(xmin, xmax, expander=0.1)
+            ymin, ymax = mtransforms.nonsingular(ymin, ymax, expander=0.1)
+
+        # In the x-direction, the hexagons exactly cover the region from
+        # xmin to xmax. Need some padding to avoid roundoff errors.
+        padding = 1.e-9 * (xmax - xmin)
+        xmin -= padding
+        xmax += padding
+        sx = (xmax - xmin) / nx
+        sy = (ymax - ymin) / ny
+
+        if marginals:
+            xorig = x.copy()
+            yorig = y.copy()
+
+        x = (x - xmin) / sx
+        y = (y - ymin) / sy
+        ix1 = np.round(x).astype(int)
+        iy1 = np.round(y).astype(int)
+        ix2 = np.floor(x).astype(int)
+        iy2 = np.floor(y).astype(int)
+
+        nx1 = nx + 1
+        ny1 = ny + 1
+        nx2 = nx
+        ny2 = ny
+        n = nx1 * ny1 + nx2 * ny2
+
+        d1 = (x - ix1) ** 2 + 3.0 * (y - iy1) ** 2
+        d2 = (x - ix2 - 0.5) ** 2 + 3.0 * (y - iy2 - 0.5) ** 2
+        bdist = (d1 < d2)
+        if C is None:
+            lattice1 = np.zeros((nx1, ny1))
+            lattice2 = np.zeros((nx2, ny2))
+            c1 = (0 <= ix1) & (ix1 < nx1) & (0 <= iy1) & (iy1 < ny1) & bdist
+            c2 = (0 <= ix2) & (ix2 < nx2) & (0 <= iy2) & (iy2 < ny2) & ~bdist
+            np.add.at(lattice1, (ix1[c1], iy1[c1]), 1)
+            np.add.at(lattice2, (ix2[c2], iy2[c2]), 1)
+            if mincnt is not None:
+                lattice1[lattice1 < mincnt] = np.nan
+                lattice2[lattice2 < mincnt] = np.nan
+            accum = np.concatenate([lattice1.ravel(), lattice2.ravel()])
+            good_idxs = ~np.isnan(accum)
+
+        else:
+            if mincnt is None:
+                mincnt = 0
+
+            # create accumulation arrays
+            lattice1 = np.empty((nx1, ny1), dtype=object)
+            for i in range(nx1):
+                for j in range(ny1):
+                    lattice1[i, j] = []
+            lattice2 = np.empty((nx2, ny2), dtype=object)
+            for i in range(nx2):
+                for j in range(ny2):
+                    lattice2[i, j] = []
+
+            for i in range(len(x)):
+                if bdist[i]:
+                    if 0 <= ix1[i] < nx1 and 0 <= iy1[i] < ny1:
+                        lattice1[ix1[i], iy1[i]].append(C[i])
+                else:
+                    if 0 <= ix2[i] < nx2 and 0 <= iy2[i] < ny2:
+                        lattice2[ix2[i], iy2[i]].append(C[i])
+
+            for i in range(nx1):
+                for j in range(ny1):
+                    vals = lattice1[i, j]
+                    if len(vals) > mincnt:
+                        lattice1[i, j] = reduce_C_function(vals)
+                    else:
+                        lattice1[i, j] = np.nan
+            for i in range(nx2):
+                for j in range(ny2):
+                    vals = lattice2[i, j]
+                    if len(vals) > mincnt:
+                        lattice2[i, j] = reduce_C_function(vals)
+                    else:
+                        lattice2[i, j] = np.nan
+
+            accum = np.hstack((lattice1.astype(float).ravel(),
+                               lattice2.astype(float).ravel()))
+            good_idxs = ~np.isnan(accum)
+
+        offsets = np.zeros((n, 2), float)
+        offsets[:nx1 * ny1, 0] = np.repeat(np.arange(nx1), ny1)
+        offsets[:nx1 * ny1, 1] = np.tile(np.arange(ny1), nx1)
+        offsets[nx1 * ny1:, 0] = np.repeat(np.arange(nx2) + 0.5, ny2)
+        offsets[nx1 * ny1:, 1] = np.tile(np.arange(ny2), nx2) + 0.5
+        offsets[:, 0] *= sx
+        offsets[:, 1] *= sy
+        offsets[:, 0] += xmin
+        offsets[:, 1] += ymin
+        # remove accumulation bins with no data
+        offsets = offsets[good_idxs, :]
+        accum = accum[good_idxs]
+
+        polygon = [sx, sy / 3] * np.array(
+            [[.5, -.5], [.5, .5], [0., 1.], [-.5, .5], [-.5, -.5], [0., -1.]])
+
+        if linewidths is None:
+            linewidths = [1.0]
+
+        if xscale == 'log' or yscale == 'log':
+            polygons = np.expand_dims(polygon, 0) + np.expand_dims(offsets, 1)
+            if xscale == 'log':
+                polygons[:, :, 0] = 10.0 ** polygons[:, :, 0]
+                xmin = 10.0 ** xmin
+                xmax = 10.0 ** xmax
+                self.set_xscale(xscale)
+            if yscale == 'log':
+                polygons[:, :, 1] = 10.0 ** polygons[:, :, 1]
+                ymin = 10.0 ** ymin
+                ymax = 10.0 ** ymax
+                self.set_yscale(yscale)
+            collection = mcoll.PolyCollection(
+                polygons,
+                edgecolors=edgecolors,
+                linewidths=linewidths,
+                )
+        else:
+            collection = mcoll.PolyCollection(
+                [polygon],
+                edgecolors=edgecolors,
+                linewidths=linewidths,
+                offsets=offsets,
+                transOffset=mtransforms.IdentityTransform(),
+                offset_position="data"
+                )
+
+        # Set normalizer if bins is 'log'
+        if bins == 'log':
+            if norm is not None:
+                cbook._warn_external("Only one of 'bins' and 'norm' "
+                                     "arguments can be supplied, ignoring "
+                                     "bins={}".format(bins))
+            else:
+                norm = mcolors.LogNorm()
+            bins = None
+
+        if isinstance(norm, mcolors.LogNorm):
+            if (accum == 0).any():
+                # make sure we have no zeros
+                accum += 1
+
+        # autoscale the norm with curren accum values if it hasn't
+        # been set
+        if norm is not None:
+            if norm.vmin is None and norm.vmax is None:
+                norm.autoscale(accum)
+
+        if bins is not None:
+            if not np.iterable(bins):
+                minimum, maximum = min(accum), max(accum)
+                bins -= 1  # one less edge than bins
+                bins = minimum + (maximum - minimum) * np.arange(bins) / bins
+            bins = np.sort(bins)
+            accum = bins.searchsorted(accum)
+
+        collection.set_array(accum)
+        collection.set_cmap(cmap)
+        collection.set_norm(norm)
+        collection.set_alpha(alpha)
+        collection.update(kwargs)
+
+        if vmin is not None or vmax is not None:
+            collection.set_clim(vmin, vmax)
+        else:
+            collection.autoscale_None()
+
+        corners = ((xmin, ymin), (xmax, ymax))
+        self.update_datalim(corners)
+        self._request_autoscale_view(tight=True)
+
+        # add the collection last
+        self.add_collection(collection, autolim=False)
+        if not marginals:
+            return collection
+
+        if C is None:
+            C = np.ones(len(x))
+
+        def coarse_bin(x, y, coarse):
+            ind = coarse.searchsorted(x).clip(0, len(coarse) - 1)
+            mus = np.zeros(len(coarse))
+            for i in range(len(coarse)):
+                yi = y[ind == i]
+                if len(yi) > 0:
+                    mu = reduce_C_function(yi)
+                else:
+                    mu = np.nan
+                mus[i] = mu
+            return mus
+
+        coarse = np.linspace(xmin, xmax, gridsize)
+
+        xcoarse = coarse_bin(xorig, C, coarse)
+        valid = ~np.isnan(xcoarse)
+        verts, values = [], []
+        for i, val in enumerate(xcoarse):
+            thismin = coarse[i]
+            if i < len(coarse) - 1:
+                thismax = coarse[i + 1]
+            else:
+                thismax = thismin + np.diff(coarse)[-1]
+
+            if not valid[i]:
+                continue
+
+            verts.append([(thismin, 0),
+                          (thismin, 0.05),
+                          (thismax, 0.05),
+                          (thismax, 0)])
+            values.append(val)
+
+        values = np.array(values)
+        trans = self.get_xaxis_transform(which='grid')
+
+        hbar = mcoll.PolyCollection(verts, transform=trans, edgecolors='face')
+
+        hbar.set_array(values)
+        hbar.set_cmap(cmap)
+        hbar.set_norm(norm)
+        hbar.set_alpha(alpha)
+        hbar.update(kwargs)
+        self.add_collection(hbar, autolim=False)
+
+        coarse = np.linspace(ymin, ymax, gridsize)
+        ycoarse = coarse_bin(yorig, C, coarse)
+        valid = ~np.isnan(ycoarse)
+        verts, values = [], []
+        for i, val in enumerate(ycoarse):
+            thismin = coarse[i]
+            if i < len(coarse) - 1:
+                thismax = coarse[i + 1]
+            else:
+                thismax = thismin + np.diff(coarse)[-1]
+            if not valid[i]:
+                continue
+            verts.append([(0, thismin), (0.0, thismax),
+                          (0.05, thismax), (0.05, thismin)])
+            values.append(val)
+
+        values = np.array(values)
+
+        trans = self.get_yaxis_transform(which='grid')
+
+        vbar = mcoll.PolyCollection(verts, transform=trans, edgecolors='face')
+        vbar.set_array(values)
+        vbar.set_cmap(cmap)
+        vbar.set_norm(norm)
+        vbar.set_alpha(alpha)
+        vbar.update(kwargs)
+        self.add_collection(vbar, autolim=False)
+
+        collection.hbar = hbar
+        collection.vbar = vbar
+
+        def on_changed(collection):
+            hbar.set_cmap(collection.get_cmap())
+            hbar.set_clim(collection.get_clim())
+            vbar.set_cmap(collection.get_cmap())
+            vbar.set_clim(collection.get_clim())
+
+        collection.callbacksSM.connect('changed', on_changed)
+
+        return collection
+
+    @docstring.dedent_interpd
+    def arrow(self, x, y, dx, dy, **kwargs):
+        """
+        Add an arrow to the axes.
+
+        This draws an arrow from ``(x, y)`` to ``(x+dx, y+dy)``.
+
+        Parameters
+        ----------
+        x, y : float
+            The x and y coordinates of the arrow base.
+        dx, dy : float
+            The length of the arrow along x and y direction.
+
+        Returns
+        -------
+        arrow : `.FancyArrow`
+            The created `.FancyArrow` object.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Optional kwargs (inherited from `.FancyArrow` patch) control the
+            arrow construction and properties:
+
+        %(FancyArrow)s
+
+        Notes
+        -----
+        The resulting arrow is affected by the axes aspect ratio and limits.
+        This may produce an arrow whose head is not square with its stem. To
+        create an arrow whose head is square with its stem,
+        use :meth:`annotate` for example:
+
+        >>> ax.annotate("", xy=(0.5, 0.5), xytext=(0, 0),
+        ...             arrowprops=dict(arrowstyle="->"))
+
+        """
+        # Strip away units for the underlying patch since units
+        # do not make sense to most patch-like code
+        x = self.convert_xunits(x)
+        y = self.convert_yunits(y)
+        dx = self.convert_xunits(dx)
+        dy = self.convert_yunits(dy)
+
+        a = mpatches.FancyArrow(x, y, dx, dy, **kwargs)
+        self.add_artist(a)
+        return a
+
+    @docstring.copy(mquiver.QuiverKey.__init__)
+    def quiverkey(self, Q, X, Y, U, label, **kw):
+        qk = mquiver.QuiverKey(Q, X, Y, U, label, **kw)
+        self.add_artist(qk)
+        return qk
+
+    # Handle units for x and y, if they've been passed
+    def _quiver_units(self, args, kw):
+        if len(args) > 3:
+            x, y = args[0:2]
+            self._process_unit_info(xdata=x, ydata=y, kwargs=kw)
+            x = self.convert_xunits(x)
+            y = self.convert_yunits(y)
+            return (x, y) + args[2:]
+        return args
+
+    # args can by a combination if X, Y, U, V, C and all should be replaced
+    @_preprocess_data()
+    def quiver(self, *args, **kw):
+        # Make sure units are handled for x and y values
+        args = self._quiver_units(args, kw)
+
+        q = mquiver.Quiver(self, *args, **kw)
+
+        self.add_collection(q, autolim=True)
+        self._request_autoscale_view()
+        return q
+    quiver.__doc__ = mquiver.Quiver.quiver_doc
+
+    # args can be some combination of X, Y, U, V, C and all should be replaced
+    @_preprocess_data()
+    @docstring.dedent_interpd
+    def barbs(self, *args, **kw):
+        """
+        %(barbs_doc)s
+        """
+        # Make sure units are handled for x and y values
+        args = self._quiver_units(args, kw)
+
+        b = mquiver.Barbs(self, *args, **kw)
+        self.add_collection(b, autolim=True)
+        self._request_autoscale_view()
+        return b
+
+    # Uses a custom implementation of data-kwarg handling in
+    # _process_plot_var_args.
+    def fill(self, *args, data=None, **kwargs):
+        """
+        Plot filled polygons.
+
+        Parameters
+        ----------
+        *args : sequence of x, y, [color]
+            Each polygon is defined by the lists of *x* and *y* positions of
+            its nodes, optionally followed by a *color* specifier. See
+            :mod:`matplotlib.colors` for supported color specifiers. The
+            standard color cycle is used for polygons without a color
+            specifier.
+
+            You can plot multiple polygons by providing multiple *x*, *y*,
+            *[color]* groups.
+
+            For example, each of the following is legal::
+
+                ax.fill(x, y)                    # a polygon with default color
+                ax.fill(x, y, "b")               # a blue polygon
+                ax.fill(x, y, x2, y2)            # two polygons
+                ax.fill(x, y, "b", x2, y2, "r")  # a blue and a red polygon
+
+        data : indexable object, optional
+            An object with labelled data. If given, provide the label names to
+            plot in *x* and *y*, e.g.::
+
+                ax.fill("time", "signal",
+                        data={"time": [0, 1, 2], "signal": [0, 1, 0]})
+
+        Returns
+        -------
+        a list of :class:`~matplotlib.patches.Polygon`
+
+        Other Parameters
+        ----------------
+        **kwargs : :class:`~matplotlib.patches.Polygon` properties
+
+        Notes
+        -----
+        Use :meth:`fill_between` if you would like to fill the region between
+        two curves.
+        """
+        # For compatibility(!), get aliases from Line2D rather than Patch.
+        kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D)
+        # _get_patches_for_fill returns a generator, convert it to a list.
+        patches = [*self._get_patches_for_fill(*args, data=data, **kwargs)]
+        for poly in patches:
+            self.add_patch(poly)
+        self._request_autoscale_view()
+        return patches
+
+    @_preprocess_data(replace_names=["x", "y1", "y2", "where"])
+    @docstring.dedent_interpd
+    def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
+                     step=None, **kwargs):
+        """
+        Fill the area between two horizontal curves.
+
+        The curves are defined by the points (*x*, *y1*) and (*x*, *y2*). This
+        creates one or multiple polygons describing the filled area.
+
+        You may exclude some horizontal sections from filling using *where*.
+
+        By default, the edges connect the given points directly. Use *step* if
+        the filling should be a step function, i.e. constant in between *x*.
+
+
+        Parameters
+        ----------
+        x : array (length N)
+            The x coordinates of the nodes defining the curves.
+
+        y1 : array (length N) or scalar
+            The y coordinates of the nodes defining the first curve.
+
+        y2 : array (length N) or scalar, optional, default: 0
+            The y coordinates of the nodes defining the second curve.
+
+        where : array of bool (length N), optional, default: None
+            Define *where* to exclude some horizontal regions from being
+            filled. The filled regions are defined by the coordinates
+            ``x[where]``.  More precisely, fill between ``x[i]`` and ``x[i+1]``
+            if ``where[i] and where[i+1]``.  Note that this definition implies
+            that an isolated *True* value between two *False* values in
+            *where* will not result in filling.  Both sides of the *True*
+            position remain unfilled due to the adjacent *False* values.
+
+        interpolate : bool, optional
+            This option is only relevant if *where* is used and the two curves
+            are crossing each other.
+
+            Semantically, *where* is often used for *y1* > *y2* or similar.
+            By default, the nodes of the polygon defining the filled region
+            will only be placed at the positions in the *x* array.  Such a
+            polygon cannot describe the above semantics close to the
+            intersection.  The x-sections containing the intersection are
+            simply clipped.
+
+            Setting *interpolate* to *True* will calculate the actual
+            intersection point and extend the filled region up to this point.
+
+        step : {'pre', 'post', 'mid'}, optional
+            Define *step* if the filling should be a step function,
+            i.e. constant in between *x*. The value determines where the
+            step will occur:
+
+            - 'pre': The y value is continued constantly to the left from
+              every *x* position, i.e. the interval ``(x[i-1], x[i]]`` has the
+              value ``y[i]``.
+            - 'post': The y value is continued constantly to the right from
+              every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
+              value ``y[i]``.
+            - 'mid': Steps occur half-way between the *x* positions.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            All other keyword arguments are passed on to `.PolyCollection`.
+            They control the `.Polygon` properties:
+
+            %(PolyCollection)s
+
+        Returns
+        -------
+        `.PolyCollection`
+            A `.PolyCollection` containing the plotted polygons.
+
+        See Also
+        --------
+        fill_betweenx : Fill between two sets of x-values.
+
+        Notes
+        -----
+        .. [notes section required to get data note injection right]
+
+        """
+        if not rcParams['_internal.classic_mode']:
+            kwargs = cbook.normalize_kwargs(kwargs, mcoll.Collection)
+            if not any(c in kwargs for c in ('color', 'facecolor')):
+                kwargs['facecolor'] = \
+                    self._get_patches_for_fill.get_next_color()
+
+        # Handle united data, such as dates
+        self._process_unit_info(xdata=x, ydata=y1, kwargs=kwargs)
+        self._process_unit_info(ydata=y2)
+
+        # Convert the arrays so we can work with them
+        x = ma.masked_invalid(self.convert_xunits(x))
+        y1 = ma.masked_invalid(self.convert_yunits(y1))
+        y2 = ma.masked_invalid(self.convert_yunits(y2))
+
+        for name, array in [('x', x), ('y1', y1), ('y2', y2)]:
+            if array.ndim > 1:
+                raise ValueError('Input passed into argument "%r"' % name +
+                                 'is not 1-dimensional.')
+
+        if where is None:
+            where = True
+        else:
+            where = np.asarray(where, dtype=bool)
+            if where.size != x.size:
+                cbook.warn_deprecated(
+                    "3.2",
+                    message="The parameter where must have the same size as x "
+                            "in fill_between(). This will become an error in "
+                            "future versions of Matplotlib.")
+        where = where & ~functools.reduce(np.logical_or,
+                                          map(np.ma.getmask, [x, y1, y2]))
+
+        x, y1, y2 = np.broadcast_arrays(np.atleast_1d(x), y1, y2)
+
+        polys = []
+        for ind0, ind1 in cbook.contiguous_regions(where):
+            xslice = x[ind0:ind1]
+            y1slice = y1[ind0:ind1]
+            y2slice = y2[ind0:ind1]
+            if step is not None:
+                step_func = cbook.STEP_LOOKUP_MAP["steps-" + step]
+                xslice, y1slice, y2slice = step_func(xslice, y1slice, y2slice)
+
+            if not len(xslice):
+                continue
+
+            N = len(xslice)
+            X = np.zeros((2 * N + 2, 2), float)
+
+            if interpolate:
+                def get_interp_point(ind):
+                    im1 = max(ind - 1, 0)
+                    x_values = x[im1:ind + 1]
+                    diff_values = y1[im1:ind + 1] - y2[im1:ind + 1]
+                    y1_values = y1[im1:ind + 1]
+
+                    if len(diff_values) == 2:
+                        if np.ma.is_masked(diff_values[1]):
+                            return x[im1], y1[im1]
+                        elif np.ma.is_masked(diff_values[0]):
+                            return x[ind], y1[ind]
+
+                    diff_order = diff_values.argsort()
+                    diff_root_x = np.interp(
+                        0, diff_values[diff_order], x_values[diff_order])
+                    x_order = x_values.argsort()
+                    diff_root_y = np.interp(diff_root_x, x_values[x_order],
+                                            y1_values[x_order])
+                    return diff_root_x, diff_root_y
+
+                start = get_interp_point(ind0)
+                end = get_interp_point(ind1)
+            else:
+                # the purpose of the next two lines is for when y2 is a
+                # scalar like 0 and we want the fill to go all the way
+                # down to 0 even if none of the y1 sample points do
+                start = xslice[0], y2slice[0]
+                end = xslice[-1], y2slice[-1]
+
+            X[0] = start
+            X[N + 1] = end
+
+            X[1:N + 1, 0] = xslice
+            X[1:N + 1, 1] = y1slice
+            X[N + 2:, 0] = xslice[::-1]
+            X[N + 2:, 1] = y2slice[::-1]
+
+            polys.append(X)
+
+        collection = mcoll.PolyCollection(polys, **kwargs)
+
+        # now update the datalim and autoscale
+        XY1 = np.array([x[where], y1[where]]).T
+        XY2 = np.array([x[where], y2[where]]).T
+        self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits,
+                                         updatex=True, updatey=True)
+        self.ignore_existing_data_limits = False
+        self.dataLim.update_from_data_xy(XY2, self.ignore_existing_data_limits,
+                                         updatex=False, updatey=True)
+        self.add_collection(collection, autolim=False)
+        self._request_autoscale_view()
+        return collection
+
+    @_preprocess_data(replace_names=["y", "x1", "x2", "where"])
+    @docstring.dedent_interpd
+    def fill_betweenx(self, y, x1, x2=0, where=None,
+                      step=None, interpolate=False, **kwargs):
+        """
+        Fill the area between two vertical curves.
+
+        The curves are defined by the points (*x1*, *y*) and (*x2*, *y*). This
+        creates one or multiple polygons describing the filled area.
+
+        You may exclude some vertical sections from filling using *where*.
+
+        By default, the edges connect the given points directly. Use *step* if
+        the filling should be a step function, i.e. constant in between *y*.
+
+
+        Parameters
+        ----------
+        y : array (length N)
+            The y coordinates of the nodes defining the curves.
+
+        x1 : array (length N) or scalar
+            The x coordinates of the nodes defining the first curve.
+
+        x2 : array (length N) or scalar, optional, default: 0
+            The x coordinates of the nodes defining the second curve.
+
+        where : array of bool (length N), optional, default: None
+            Define *where* to exclude some vertical regions from being
+            filled. The filled regions are defined by the coordinates
+            ``y[where]``.  More precisely, fill between ``y[i]`` and ``y[i+1]``
+            if ``where[i] and where[i+1]``.  Note that this definition implies
+            that an isolated *True* value between two *False* values in
+            *where* will not result in filling.  Both sides of the *True*
+            position remain unfilled due to the adjacent *False* values.
+
+        interpolate : bool, optional
+            This option is only relevant if *where* is used and the two curves
+            are crossing each other.
+
+            Semantically, *where* is often used for *x1* > *x2* or similar.
+            By default, the nodes of the polygon defining the filled region
+            will only be placed at the positions in the *y* array.  Such a
+            polygon cannot describe the above semantics close to the
+            intersection.  The y-sections containing the intersection are
+            simply clipped.
+
+            Setting *interpolate* to *True* will calculate the actual
+            intersection point and extend the filled region up to this point.
+
+        step : {'pre', 'post', 'mid'}, optional
+            Define *step* if the filling should be a step function,
+            i.e. constant in between *y*. The value determines where the
+            step will occur:
+
+            - 'pre': The y value is continued constantly to the left from
+              every *x* position, i.e. the interval ``(x[i-1], x[i]]`` has the
+              value ``y[i]``.
+            - 'post': The y value is continued constantly to the right from
+              every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
+              value ``y[i]``.
+            - 'mid': Steps occur half-way between the *x* positions.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            All other keyword arguments are passed on to `.PolyCollection`.
+            They control the `.Polygon` properties:
+
+            %(PolyCollection)s
+
+        Returns
+        -------
+        `.PolyCollection`
+            A `.PolyCollection` containing the plotted polygons.
+
+        See Also
+        --------
+        fill_between : Fill between two sets of y-values.
+
+        Notes
+        -----
+        .. [notes section required to get data note injection right]
+
+        """
+        if not rcParams['_internal.classic_mode']:
+            kwargs = cbook.normalize_kwargs(kwargs, mcoll.Collection)
+            if not any(c in kwargs for c in ('color', 'facecolor')):
+                kwargs['facecolor'] = \
+                    self._get_patches_for_fill.get_next_color()
+
+        # Handle united data, such as dates
+        self._process_unit_info(ydata=y, xdata=x1, kwargs=kwargs)
+        self._process_unit_info(xdata=x2)
+
+        # Convert the arrays so we can work with them
+        y = ma.masked_invalid(self.convert_yunits(y))
+        x1 = ma.masked_invalid(self.convert_xunits(x1))
+        x2 = ma.masked_invalid(self.convert_xunits(x2))
+
+        for name, array in [('y', y), ('x1', x1), ('x2', x2)]:
+            if array.ndim > 1:
+                raise ValueError('Input passed into argument "%r"' % name +
+                                 'is not 1-dimensional.')
+
+        if where is None:
+            where = True
+        else:
+            where = np.asarray(where, dtype=bool)
+            if where.size != y.size:
+                cbook.warn_deprecated(
+                    "3.2",
+                    message="The parameter where must have the same size as y "
+                            "in fill_between(). This will become an error in "
+                            "future versions of Matplotlib.")
+        where = where & ~functools.reduce(np.logical_or,
+                                          map(np.ma.getmask, [y, x1, x2]))
+
+        y, x1, x2 = np.broadcast_arrays(np.atleast_1d(y), x1, x2)
+
+        polys = []
+        for ind0, ind1 in cbook.contiguous_regions(where):
+            yslice = y[ind0:ind1]
+            x1slice = x1[ind0:ind1]
+            x2slice = x2[ind0:ind1]
+            if step is not None:
+                step_func = cbook.STEP_LOOKUP_MAP["steps-" + step]
+                yslice, x1slice, x2slice = step_func(yslice, x1slice, x2slice)
+
+            if not len(yslice):
+                continue
+
+            N = len(yslice)
+            Y = np.zeros((2 * N + 2, 2), float)
+            if interpolate:
+                def get_interp_point(ind):
+                    im1 = max(ind - 1, 0)
+                    y_values = y[im1:ind + 1]
+                    diff_values = x1[im1:ind + 1] - x2[im1:ind + 1]
+                    x1_values = x1[im1:ind + 1]
+
+                    if len(diff_values) == 2:
+                        if np.ma.is_masked(diff_values[1]):
+                            return x1[im1], y[im1]
+                        elif np.ma.is_masked(diff_values[0]):
+                            return x1[ind], y[ind]
+
+                    diff_order = diff_values.argsort()
+                    diff_root_y = np.interp(
+                        0, diff_values[diff_order], y_values[diff_order])
+                    y_order = y_values.argsort()
+                    diff_root_x = np.interp(diff_root_y, y_values[y_order],
+                                            x1_values[y_order])
+                    return diff_root_x, diff_root_y
+
+                start = get_interp_point(ind0)
+                end = get_interp_point(ind1)
+            else:
+                # the purpose of the next two lines is for when x2 is a
+                # scalar like 0 and we want the fill to go all the way
+                # down to 0 even if none of the x1 sample points do
+                start = x2slice[0], yslice[0]
+                end = x2slice[-1], yslice[-1]
+
+            Y[0] = start
+            Y[N + 1] = end
+
+            Y[1:N + 1, 0] = x1slice
+            Y[1:N + 1, 1] = yslice
+            Y[N + 2:, 0] = x2slice[::-1]
+            Y[N + 2:, 1] = yslice[::-1]
+
+            polys.append(Y)
+
+        collection = mcoll.PolyCollection(polys, **kwargs)
+
+        # now update the datalim and autoscale
+        X1Y = np.array([x1[where], y[where]]).T
+        X2Y = np.array([x2[where], y[where]]).T
+        self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits,
+                                         updatex=True, updatey=True)
+        self.ignore_existing_data_limits = False
+        self.dataLim.update_from_data_xy(X2Y, self.ignore_existing_data_limits,
+                                         updatex=True, updatey=False)
+        self.add_collection(collection, autolim=False)
+        self._request_autoscale_view()
+        return collection
+
+    #### plotting z(x, y): imshow, pcolor and relatives, contour
+    @_preprocess_data()
+    @cbook._delete_parameter("3.1", "shape")
+    @cbook._delete_parameter("3.1", "imlim")
+    def imshow(self, X, cmap=None, norm=None, aspect=None,
+               interpolation=None, alpha=None, vmin=None, vmax=None,
+               origin=None, extent=None, shape=None, filternorm=1,
+               filterrad=4.0, imlim=None, resample=None, url=None, **kwargs):
+        """
+        Display data as an image; i.e. on a 2D regular raster.
+
+        The input may either be actual RGB(A) data, or 2D scalar data, which
+        will be rendered as a pseudocolor image. Note: For actually displaying
+        a grayscale image set up the color mapping using the parameters
+        ``cmap='gray', vmin=0, vmax=255``.
+
+        Parameters
+        ----------
+        X : array-like or PIL image
+            The image data. Supported array shapes are:
+
+            - (M, N): an image with scalar data. The values are mapped to
+              colors using normalization and a colormap. See parameters *norm*,
+              *cmap*, *vmin*, *vmax*.
+            - (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
+            - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
+              i.e. including transparency.
+
+            The first two dimensions (M, N) define the rows and columns of
+            the image.
+
+            Out-of-range RGB(A) values are clipped.
+
+        cmap : str or `~matplotlib.colors.Colormap`, optional
+            The Colormap instance or registered colormap name used to map
+            scalar data to colors. This parameter is ignored for RGB(A) data.
+            Defaults to :rc:`image.cmap`.
+
+        norm : `~matplotlib.colors.Normalize`, optional
+            The `Normalize` instance used to scale scalar data to the [0, 1]
+            range before mapping to colors using *cmap*. By default, a linear
+            scaling mapping the lowest value to 0 and the highest to 1 is used.
+            This parameter is ignored for RGB(A) data.
+
+        aspect : {'equal', 'auto'} or float, optional
+            Controls the aspect ratio of the axes. The aspect is of particular
+            relevance for images since it may distort the image, i.e. pixel
+            will not be square.
+
+            This parameter is a shortcut for explicitly calling
+            `.Axes.set_aspect`. See there for further details.
+
+            - 'equal': Ensures an aspect ratio of 1. Pixels will be square
+              (unless pixel sizes are explicitly made non-square in data
+              coordinates using *extent*).
+            - 'auto': The axes is kept fixed and the aspect is adjusted so
+              that the data fit in the axes. In general, this will result in
+              non-square pixels.
+
+            If not given, use :rc:`image.aspect`.
+
+        interpolation : str, optional
+            The interpolation method used. If *None*, :rc:`image.interpolation`
+            is used.
+
+            Supported values are 'none', 'antialiased', 'nearest', 'bilinear',
+            'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
+            'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
+            'sinc', 'lanczos'.
+
+            If *interpolation* is 'none', then no interpolation is performed
+            on the Agg, ps, pdf and svg backends. Other backends will fall back
+            to 'nearest'. Note that most SVG renders perform interpolation at
+            rendering and that the default interpolation method they implement
+            may differ.
+
+            If *interpolation* is the default 'antialiased', then 'nearest'
+            interpolation is used if the image is upsampled by more than a
+            factor of three (i.e. the number of display pixels is at least
+            three times the size of the data array).  If the upsampling rate is
+            smaller than 3, or the image is downsampled, then 'hanning'
+            interpolation is used to act as an anti-aliasing filter, unless the
+            image happens to be upsampled by exactly a factor of two or one.
+
+            See
+            :doc:`/gallery/images_contours_and_fields/interpolation_methods`
+            for an overview of the supported interpolation methods, and
+            :doc:`/gallery/images_contours_and_fields/image_antialiasing` for
+            a discussion of image antialiasing.
+
+            Some interpolation methods require an additional radius parameter,
+            which can be set by *filterrad*. Additionally, the antigrain image
+            resize filter is controlled by the parameter *filternorm*.
+
+        alpha : scalar or array-like, optional
+            The alpha blending value, between 0 (transparent) and 1 (opaque).
+            If *alpha* is an array, the alpha blending values are applied pixel
+            by pixel, and *alpha* must have the same shape as *X*.
+
+        vmin, vmax : scalar, optional
+            When using scalar data and no explicit *norm*, *vmin* and *vmax*
+            define the data range that the colormap covers. By default,
+            the colormap covers the complete value range of the supplied
+            data. *vmin*, *vmax* are ignored if the *norm* parameter is used.
+
+        origin : {'upper', 'lower'}, optional
+            Place the [0, 0] index of the array in the upper left or lower left
+            corner of the axes. The convention 'upper' is typically used for
+            matrices and images.
+            If not given, :rc:`image.origin` is used, defaulting to 'upper'.
+
+            Note that the vertical axes points upward for 'lower'
+            but downward for 'upper'.
+
+            See the :doc:`/tutorials/intermediate/imshow_extent` tutorial for
+            examples and a more detailed description.
+
+        extent : scalars (left, right, bottom, top), optional
+            The bounding box in data coordinates that the image will fill.
+            The image is stretched individually along x and y to fill the box.
+
+            The default extent is determined by the following conditions.
+            Pixels have unit size in data coordinates. Their centers are on
+            integer coordinates, and their center coordinates range from 0 to
+            columns-1 horizontally and from 0 to rows-1 vertically.
+
+            Note that the direction of the vertical axis and thus the default
+            values for top and bottom depend on *origin*:
+
+            - For ``origin == 'upper'`` the default is
+              ``(-0.5, numcols-0.5, numrows-0.5, -0.5)``.
+            - For ``origin == 'lower'`` the default is
+              ``(-0.5, numcols-0.5, -0.5, numrows-0.5)``.
+
+            See the :doc:`/tutorials/intermediate/imshow_extent` tutorial for
+            examples and a more detailed description.
+
+        filternorm : bool, optional, default: True
+            A parameter for the antigrain image resize filter (see the
+            antigrain documentation).  If *filternorm* is set, the filter
+            normalizes integer values and corrects the rounding errors. It
+            doesn't do anything with the source floating point values, it
+            corrects only integers according to the rule of 1.0 which means
+            that any sum of pixel weights must be equal to 1.0.  So, the
+            filter function must produce a graph of the proper shape.
+
+        filterrad : float > 0, optional, default: 4.0
+            The filter radius for filters that have a radius parameter, i.e.
+            when interpolation is one of: 'sinc', 'lanczos' or 'blackman'.
+
+        resample : bool, optional
+            When *True*, use a full resampling method.  When *False*, only
+            resample when the output image is larger than the input image.
+
+        url : str, optional
+            Set the url of the created `.AxesImage`. See `.Artist.set_url`.
+
+        Returns
+        -------
+        image : `~matplotlib.image.AxesImage`
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.artist.Artist` properties
+            These parameters are passed on to the constructor of the
+            `.AxesImage` artist.
+
+        See also
+        --------
+        matshow : Plot a matrix or an array as an image.
+
+        Notes
+        -----
+        Unless *extent* is used, pixel centers will be located at integer
+        coordinates. In other words: the origin will coincide with the center
+        of pixel (0, 0).
+
+        There are two common representations for RGB images with an alpha
+        channel:
+
+        -   Straight (unassociated) alpha: R, G, and B channels represent the
+            color of the pixel, disregarding its opacity.
+        -   Premultiplied (associated) alpha: R, G, and B channels represent
+            the color of the pixel, adjusted for its opacity by multiplication.
+
+        `~matplotlib.pyplot.imshow` expects RGB images adopting the straight
+        (unassociated) alpha representation.
+        """
+        if aspect is None:
+            aspect = rcParams['image.aspect']
+        self.set_aspect(aspect)
+        im = mimage.AxesImage(self, cmap, norm, interpolation, origin, extent,
+                              filternorm=filternorm, filterrad=filterrad,
+                              resample=resample, **kwargs)
+
+        im.set_data(X)
+        im.set_alpha(alpha)
+        if im.get_clip_path() is None:
+            # image does not already have clipping set, clip to axes patch
+            im.set_clip_path(self.patch)
+        if vmin is not None or vmax is not None:
+            im.set_clim(vmin, vmax)
+        else:
+            im.autoscale_None()
+        im.set_url(url)
+
+        # update ax.dataLim, and, if autoscaling, set viewLim
+        # to tightly fit the image, regardless of dataLim.
+        im.set_extent(im.get_extent())
+
+        self.add_image(im)
+        return im
+
+    @staticmethod
+    def _pcolorargs(funcname, *args, allmatch=False):
+        # If allmatch is True, then the incoming X, Y, C must have matching
+        # dimensions, taking into account that X and Y can be 1-D rather than
+        # 2-D.  This perfect match is required for Gouraud shading.  For flat
+        # shading, X and Y specify boundaries, so we need one more boundary
+        # than color in each direction.  For convenience, and consistent with
+        # Matlab, we discard the last row and/or column of C if necessary to
+        # meet this condition.  This is done if allmatch is False.
+
+        if len(args) == 1:
+            C = np.asanyarray(args[0])
+            nrows, ncols = C.shape
+            if allmatch:
+                X, Y = np.meshgrid(np.arange(ncols), np.arange(nrows))
+            else:
+                X, Y = np.meshgrid(np.arange(ncols + 1), np.arange(nrows + 1))
+            C = cbook.safe_masked_invalid(C)
+            return X, Y, C
+
+        if len(args) == 3:
+            # Check x and y for bad data...
+            C = np.asanyarray(args[2])
+            X, Y = [cbook.safe_masked_invalid(a) for a in args[:2]]
+            if funcname == 'pcolormesh':
+                if np.ma.is_masked(X) or np.ma.is_masked(Y):
+                    raise ValueError(
+                        'x and y arguments to pcolormesh cannot have '
+                        'non-finite values or be of type '
+                        'numpy.ma.core.MaskedArray with masked values')
+                # safe_masked_invalid() returns an ndarray for dtypes other
+                # than floating point.
+                if isinstance(X, np.ma.core.MaskedArray):
+                    X = X.data  # strip mask as downstream doesn't like it...
+                if isinstance(Y, np.ma.core.MaskedArray):
+                    Y = Y.data
+            nrows, ncols = C.shape
+        else:
+            raise TypeError(
+                'Illegal arguments to %s; see help(%s)' % (funcname, funcname))
+
+        Nx = X.shape[-1]
+        Ny = Y.shape[0]
+        if X.ndim != 2 or X.shape[0] == 1:
+            x = X.reshape(1, Nx)
+            X = x.repeat(Ny, axis=0)
+        if Y.ndim != 2 or Y.shape[1] == 1:
+            y = Y.reshape(Ny, 1)
+            Y = y.repeat(Nx, axis=1)
+        if X.shape != Y.shape:
+            raise TypeError(
+                'Incompatible X, Y inputs to %s; see help(%s)' % (
+                funcname, funcname))
+        if allmatch:
+            if (Nx, Ny) != (ncols, nrows):
+                raise TypeError('Dimensions of C %s are incompatible with'
+                                ' X (%d) and/or Y (%d); see help(%s)' % (
+                                    C.shape, Nx, Ny, funcname))
+        else:
+            if not (ncols in (Nx, Nx - 1) and nrows in (Ny, Ny - 1)):
+                raise TypeError('Dimensions of C %s are incompatible with'
+                                ' X (%d) and/or Y (%d); see help(%s)' % (
+                                    C.shape, Nx, Ny, funcname))
+            C = C[:Ny - 1, :Nx - 1]
+        C = cbook.safe_masked_invalid(C)
+        return X, Y, C
+
+    @_preprocess_data()
+    @docstring.dedent_interpd
+    def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
+               vmax=None, **kwargs):
+        r"""
+        Create a pseudocolor plot with a non-regular rectangular grid.
+
+        Call signature::
+
+            pcolor([X, Y,] C, **kwargs)
+
+        *X* and *Y* can be used to specify the corners of the quadrilaterals.
+
+        .. hint::
+
+            ``pcolor()`` can be very slow for large arrays. In most
+            cases you should use the similar but much faster
+            `~.Axes.pcolormesh` instead. See there for a discussion of the
+            differences.
+
+        Parameters
+        ----------
+        C : array-like
+            A scalar 2-D array. The values will be color-mapped.
+
+        X, Y : array-like, optional
+            The coordinates of the quadrilateral corners. The quadrilateral
+            for ``C[i, j]`` has corners at::
+
+                (X[i+1, j], Y[i+1, j])           (X[i+1, j+1], Y[i+1, j+1])
+                                      +---------+
+                                      | C[i, j] |
+                                      +---------+
+                    (X[i, j], Y[i, j])           (X[i, j+1], Y[i, j+1])
+
+            Note that the column index corresponds to the
+            x-coordinate, and the row index corresponds to y. For
+            details, see the :ref:`Notes <axes-pcolor-grid-orientation>`
+            section below.
+
+            The dimensions of *X* and *Y* should be one greater than those of
+            *C*. Alternatively, *X*, *Y* and *C* may have equal dimensions, in
+            which case the last row and column of *C* will be ignored.
+
+            If *X* and/or *Y* are 1-D arrays or column vectors they will be
+            expanded as needed into the appropriate 2-D arrays, making a
+            rectangular grid.
+
+        cmap : str or `~matplotlib.colors.Colormap`, optional
+            A Colormap instance or registered colormap name. The colormap
+            maps the *C* values to colors. Defaults to :rc:`image.cmap`.
+
+        norm : `~matplotlib.colors.Normalize`, optional
+            The Normalize instance scales the data values to the canonical
+            colormap range [0, 1] for mapping to colors. By default, the data
+            range is mapped to the colorbar range using linear scaling.
+
+        vmin, vmax : scalar, optional, default: None
+            The colorbar range. If *None*, suitable min/max values are
+            automatically chosen by the `~.Normalize` instance (defaults to
+            the respective min/max values of *C* in case of the default linear
+            scaling).
+
+        edgecolors : {'none', None, 'face', color, color sequence}, optional
+            The color of the edges. Defaults to 'none'. Possible values:
+
+            - 'none' or '': No edge.
+            - *None*: :rc:`patch.edgecolor` will be used. Note that currently
+              :rc:`patch.force_edgecolor` has to be True for this to work.
+            - 'face': Use the adjacent face color.
+            - A color or sequence of colors will set the edge color.
+
+            The singular form *edgecolor* works as an alias.
+
+        alpha : scalar, optional, default: None
+            The alpha blending value of the face color, between 0 (transparent)
+            and 1 (opaque). Note: The edgecolor is currently not affected by
+            this.
+
+        snap : bool, optional, default: False
+            Whether to snap the mesh to pixel boundaries.
+
+        Returns
+        -------
+        collection : `matplotlib.collections.Collection`
+
+        Other Parameters
+        ----------------
+        antialiaseds : bool, optional, default: False
+            The default *antialiaseds* is False if the default
+            *edgecolors*\ ="none" is used.  This eliminates artificial lines
+            at patch boundaries, and works regardless of the value of alpha.
+            If *edgecolors* is not "none", then the default *antialiaseds*
+            is taken from :rc:`patch.antialiased`.
+            Stroking the edges may be preferred if *alpha* is 1, but will
+            cause artifacts otherwise.
+
+        **kwargs
+            Additionally, the following arguments are allowed. They are passed
+            along to the `~matplotlib.collections.PolyCollection` constructor:
+
+        %(PolyCollection)s
+
+        See Also
+        --------
+        pcolormesh : for an explanation of the differences between
+            pcolor and pcolormesh.
+        imshow : If *X* and *Y* are each equidistant, `~.Axes.imshow` can be a
+            faster alternative.
+
+        Notes
+        -----
+        **Masked arrays**
+
+        *X*, *Y* and *C* may be masked arrays. If either ``C[i, j]``, or one
+        of the vertices surrounding ``C[i, j]`` (*X* or *Y* at
+        ``[i, j], [i+1, j], [i, j+1], [i+1, j+1]``) is masked, nothing is
+        plotted.
+
+        .. _axes-pcolor-grid-orientation:
+
+        **Grid orientation**
+
+        The grid orientation follows the standard matrix convention: An array
+        *C* with shape (nrows, ncolumns) is plotted with the column number as
+        *X* and the row number as *Y*.
+
+        **Handling of pcolor() end-cases**
+
+        ``pcolor()`` displays all columns of *C* if *X* and *Y* are not
+        specified, or if *X* and *Y* have one more column than *C*.
+        If *X* and *Y* have the same number of columns as *C* then the last
+        column of *C* is dropped. Similarly for the rows.
+
+        Note: This behavior is different from MATLAB's ``pcolor()``, which
+        always discards the last row and column of *C*.
+        """
+        X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False)
+        Ny, Nx = X.shape
+
+        # unit conversion allows e.g. datetime objects as axis values
+        self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
+        X = self.convert_xunits(X)
+        Y = self.convert_yunits(Y)
+
+        # convert to MA, if necessary.
+        C = ma.asarray(C)
+        X = ma.asarray(X)
+        Y = ma.asarray(Y)
+
+        mask = ma.getmaskarray(X) + ma.getmaskarray(Y)
+        xymask = (mask[0:-1, 0:-1] + mask[1:, 1:] +
+                  mask[0:-1, 1:] + mask[1:, 0:-1])
+        # don't plot if C or any of the surrounding vertices are masked.
+        mask = ma.getmaskarray(C) + xymask
+
+        unmask = ~mask
+        X1 = ma.filled(X[:-1, :-1])[unmask]
+        Y1 = ma.filled(Y[:-1, :-1])[unmask]
+        X2 = ma.filled(X[1:, :-1])[unmask]
+        Y2 = ma.filled(Y[1:, :-1])[unmask]
+        X3 = ma.filled(X[1:, 1:])[unmask]
+        Y3 = ma.filled(Y[1:, 1:])[unmask]
+        X4 = ma.filled(X[:-1, 1:])[unmask]
+        Y4 = ma.filled(Y[:-1, 1:])[unmask]
+        npoly = len(X1)
+
+        xy = np.stack([X1, Y1, X2, Y2, X3, Y3, X4, Y4, X1, Y1], axis=-1)
+        verts = xy.reshape((npoly, 5, 2))
+
+        C = ma.filled(C[:Ny - 1, :Nx - 1])[unmask]
+
+        linewidths = (0.25,)
+        if 'linewidth' in kwargs:
+            kwargs['linewidths'] = kwargs.pop('linewidth')
+        kwargs.setdefault('linewidths', linewidths)
+
+        if 'edgecolor' in kwargs:
+            kwargs['edgecolors'] = kwargs.pop('edgecolor')
+        ec = kwargs.setdefault('edgecolors', 'none')
+
+        # aa setting will default via collections to patch.antialiased
+        # unless the boundary is not stroked, in which case the
+        # default will be False; with unstroked boundaries, aa
+        # makes artifacts that are often disturbing.
+        if 'antialiased' in kwargs:
+            kwargs['antialiaseds'] = kwargs.pop('antialiased')
+        if 'antialiaseds' not in kwargs and cbook._str_lower_equal(ec, "none"):
+            kwargs['antialiaseds'] = False
+
+        kwargs.setdefault('snap', False)
+
+        collection = mcoll.PolyCollection(verts, **kwargs)
+
+        collection.set_alpha(alpha)
+        collection.set_array(C)
+        collection.set_cmap(cmap)
+        collection.set_norm(norm)
+        collection.set_clim(vmin, vmax)
+        collection.autoscale_None()
+        self.grid(False)
+
+        x = X.compressed()
+        y = Y.compressed()
+
+        # Transform from native to data coordinates?
+        t = collection._transform
+        if (not isinstance(t, mtransforms.Transform) and
+            hasattr(t, '_as_mpl_transform')):
+            t = t._as_mpl_transform(self.axes)
+
+        if t and any(t.contains_branch_seperately(self.transData)):
+            trans_to_data = t - self.transData
+            pts = np.vstack([x, y]).T.astype(float)
+            transformed_pts = trans_to_data.transform(pts)
+            x = transformed_pts[..., 0]
+            y = transformed_pts[..., 1]
+
+        self.add_collection(collection, autolim=False)
+
+        minx = np.min(x)
+        maxx = np.max(x)
+        miny = np.min(y)
+        maxy = np.max(y)
+        collection.sticky_edges.x[:] = [minx, maxx]
+        collection.sticky_edges.y[:] = [miny, maxy]
+        corners = (minx, miny), (maxx, maxy)
+        self.update_datalim(corners)
+        self._request_autoscale_view()
+        return collection
+
+    @_preprocess_data()
+    @docstring.dedent_interpd
+    def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
+                   vmax=None, shading='flat', antialiased=False, **kwargs):
+        """
+        Create a pseudocolor plot with a non-regular rectangular grid.
+
+        Call signature::
+
+            pcolor([X, Y,] C, **kwargs)
+
+        *X* and *Y* can be used to specify the corners of the quadrilaterals.
+
+        .. note::
+
+           `~Axes.pcolormesh` is similar to `~Axes.pcolor`. It's much faster
+           and preferred in most cases. For a detailed discussion on the
+           differences see :ref:`Differences between pcolor() and pcolormesh()
+           <differences-pcolor-pcolormesh>`.
+
+        Parameters
+        ----------
+        C : array-like
+            A scalar 2-D array. The values will be color-mapped.
+
+        X, Y : array-like, optional
+            The coordinates of the quadrilateral corners. The quadrilateral
+            for ``C[i, j]`` has corners at::
+
+                (X[i+1, j], Y[i+1, j])           (X[i+1, j+1], Y[i+1, j+1])
+                                      +---------+
+                                      | C[i, j] |
+                                      +---------+
+                    (X[i, j], Y[i, j])           (X[i, j+1], Y[i, j+1])
+
+            Note that the column index corresponds to the
+            x-coordinate, and the row index corresponds to y. For
+            details, see the :ref:`Notes <axes-pcolormesh-grid-orientation>`
+            section below.
+
+            The dimensions of *X* and *Y* should be one greater than those of
+            *C*. Alternatively, *X*, *Y* and *C* may have equal dimensions, in
+            which case the last row and column of *C* will be ignored.
+
+            If *X* and/or *Y* are 1-D arrays or column vectors they will be
+            expanded as needed into the appropriate 2-D arrays, making a
+            rectangular grid.
+
+        cmap : str or `~matplotlib.colors.Colormap`, optional
+            A Colormap instance or registered colormap name. The colormap
+            maps the *C* values to colors. Defaults to :rc:`image.cmap`.
+
+        norm : `~matplotlib.colors.Normalize`, optional
+            The Normalize instance scales the data values to the canonical
+            colormap range [0, 1] for mapping to colors. By default, the data
+            range is mapped to the colorbar range using linear scaling.
+
+        vmin, vmax : scalar, optional, default: None
+            The colorbar range. If *None*, suitable min/max values are
+            automatically chosen by the `~.Normalize` instance (defaults to
+            the respective min/max values of *C* in case of the default linear
+            scaling).
+
+        edgecolors : {'none', None, 'face', color, color sequence}, optional
+            The color of the edges. Defaults to 'none'. Possible values:
+
+            - 'none' or '': No edge.
+            - *None*: :rc:`patch.edgecolor` will be used. Note that currently
+              :rc:`patch.force_edgecolor` has to be True for this to work.
+            - 'face': Use the adjacent face color.
+            - A color or sequence of colors will set the edge color.
+
+            The singular form *edgecolor* works as an alias.
+
+        alpha : scalar, optional, default: None
+            The alpha blending value, between 0 (transparent) and 1 (opaque).
+
+        shading : {'flat', 'gouraud'}, optional
+            The fill style, Possible values:
+
+            - 'flat': A solid color is used for each quad. The color of the
+              quad (i, j), (i+1, j), (i, j+1), (i+1, j+1) is given by
+              ``C[i, j]``.
+            - 'gouraud': Each quad will be Gouraud shaded: The color of the
+              corners (i', j') are given by ``C[i',j']``. The color values of
+              the area in between is interpolated from the corner values.
+              When Gouraud shading is used, *edgecolors* is ignored.
+
+        snap : bool, optional, default: False
+            Whether to snap the mesh to pixel boundaries.
+
+        Returns
+        -------
+        mesh : `matplotlib.collections.QuadMesh`
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Additionally, the following arguments are allowed. They are passed
+            along to the `~matplotlib.collections.QuadMesh` constructor:
+
+        %(QuadMesh)s
+
+        See Also
+        --------
+        pcolor : An alternative implementation with slightly different
+            features. For a detailed discussion on the differences see
+            :ref:`Differences between pcolor() and pcolormesh()
+            <differences-pcolor-pcolormesh>`.
+        imshow : If *X* and *Y* are each equidistant, `~.Axes.imshow` can be a
+            faster alternative.
+
+        Notes
+        -----
+        **Masked arrays**
+
+        *C* may be a masked array. If ``C[i, j]`` is masked, the corresponding
+        quadrilateral will be transparent. Masking of *X* and *Y* is not
+        supported. Use `~.Axes.pcolor` if you need this functionality.
+
+        .. _axes-pcolormesh-grid-orientation:
+
+        **Grid orientation**
+
+        The grid orientation follows the standard matrix convention: An array
+        *C* with shape (nrows, ncolumns) is plotted with the column number as
+        *X* and the row number as *Y*.
+
+        .. _differences-pcolor-pcolormesh:
+
+        **Differences between pcolor() and pcolormesh()**
+
+        Both methods are used to create a pseudocolor plot of a 2-D array
+        using quadrilaterals.
+
+        The main difference lies in the created object and internal data
+        handling:
+        While `~.Axes.pcolor` returns a `.PolyCollection`, `~.Axes.pcolormesh`
+        returns a `.QuadMesh`. The latter is more specialized for the given
+        purpose and thus is faster. It should almost always be preferred.
+
+        There is also a slight difference in the handling of masked arrays.
+        Both `~.Axes.pcolor` and `~.Axes.pcolormesh` support masked arrays
+        for *C*. However, only `~.Axes.pcolor` supports masked arrays for *X*
+        and *Y*. The reason lies in the internal handling of the masked values.
+        `~.Axes.pcolor` leaves out the respective polygons from the
+        PolyCollection. `~.Axes.pcolormesh` sets the facecolor of the masked
+        elements to transparent. You can see the difference when using
+        edgecolors. While all edges are drawn irrespective of masking in a
+        QuadMesh, the edge between two adjacent masked quadrilaterals in
+        `~.Axes.pcolor` is not drawn as the corresponding polygons do not
+        exist in the PolyCollection.
+
+        Another difference is the support of Gouraud shading in
+        `~.Axes.pcolormesh`, which is not available with `~.Axes.pcolor`.
+
+        """
+        shading = shading.lower()
+        kwargs.setdefault('edgecolors', 'None')
+
+        allmatch = (shading == 'gouraud')
+
+        X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch)
+        Ny, Nx = X.shape
+        X = X.ravel()
+        Y = Y.ravel()
+        # unit conversion allows e.g. datetime objects as axis values
+        self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
+        X = self.convert_xunits(X)
+        Y = self.convert_yunits(Y)
+
+        # convert to one dimensional arrays
+        C = C.ravel()
+        coords = np.column_stack((X, Y)).astype(float, copy=False)
+        collection = mcoll.QuadMesh(Nx - 1, Ny - 1, coords,
+                                    antialiased=antialiased, shading=shading,
+                                    **kwargs)
+        collection.set_alpha(alpha)
+        collection.set_array(C)
+        collection.set_cmap(cmap)
+        collection.set_norm(norm)
+        collection.set_clim(vmin, vmax)
+        collection.autoscale_None()
+
+        self.grid(False)
+
+        # Transform from native to data coordinates?
+        t = collection._transform
+        if (not isinstance(t, mtransforms.Transform) and
+            hasattr(t, '_as_mpl_transform')):
+            t = t._as_mpl_transform(self.axes)
+
+        if t and any(t.contains_branch_seperately(self.transData)):
+            trans_to_data = t - self.transData
+            coords = trans_to_data.transform(coords)
+
+        self.add_collection(collection, autolim=False)
+
+        minx, miny = np.min(coords, axis=0)
+        maxx, maxy = np.max(coords, axis=0)
+        collection.sticky_edges.x[:] = [minx, maxx]
+        collection.sticky_edges.y[:] = [miny, maxy]
+        corners = (minx, miny), (maxx, maxy)
+        self.update_datalim(corners)
+        self._request_autoscale_view()
+        return collection
+
+    @_preprocess_data()
+    @docstring.dedent_interpd
+    def pcolorfast(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
+                   vmax=None, **kwargs):
+        """
+        Create a pseudocolor plot with a non-regular rectangular grid.
+
+        Call signature::
+
+          ax.pcolorfast([X, Y], C, /, **kwargs)
+
+        This method is similar to ~.Axes.pcolor` and `~.Axes.pcolormesh`.
+        It's designed to provide the fastest pcolor-type plotting with the
+        Agg backend. To achieve this, it uses different algorithms internally
+        depending on the complexity of the input grid (regular rectangular,
+        non-regular rectangular or arbitrary quadrilateral).
+
+        .. warning::
+
+           This method is experimental. Compared to `~.Axes.pcolor` or
+           `~.Axes.pcolormesh` it has some limitations:
+
+           - It supports only flat shading (no outlines)
+           - It lacks support for log scaling of the axes.
+           - It does not have a have a pyplot wrapper.
+
+        Parameters
+        ----------
+        C : array-like(M, N)
+            The image data. Supported array shapes are:
+
+            - (M, N): an image with scalar data. The data is visualized
+              using a colormap.
+            - (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
+            - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
+              i.e. including transparency.
+
+            The first two dimensions (M, N) define the rows and columns of
+            the image.
+
+            This parameter can only be passed positionally.
+
+        X, Y : tuple or array-like, default: ``(0, N)``, ``(0, M)``
+            *X* and *Y* are used to specify the coordinates of the
+            quadrilaterals. There are different ways to do this:
+
+            - Use tuples ``X=(xmin, xmax)`` and ``Y=(ymin, ymax)`` to define
+              a *uniform rectangular grid*.
+
+              The tuples define the outer edges of the grid. All individual
+              quadrilaterals will be of the same size. This is the fastest
+              version.
+
+            - Use 1D arrays *X*, *Y* to specify a *non-uniform rectangular
+              grid*.
+
+              In this case *X* and *Y* have to be monotonic 1D arrays of length
+              *N+1* and *M+1*, specifying the x and y boundaries of the cells.
+
+              The speed is intermediate. Note: The grid is checked, and if
+              found to be uniform the fast version is used.
+
+            - Use 2D arrays *X*, *Y* if you need an *arbitrary quadrilateral
+              grid* (i.e. if the quadrilaterals are not rectangular).
+
+              In this case *X* and *Y* are 2D arrays with shape (M + 1, N + 1),
+              specifying the x and y coordinates of the corners of the colored
+              quadrilaterals.
+
+              This is the most general, but the slowest to render.  It may
+              produce faster and more compact output using ps, pdf, and
+              svg backends, however.
+
+            These arguments can only be passed positionally.
+
+        cmap : str or `~matplotlib.colors.Colormap`, optional
+            A Colormap instance or registered colormap name. The colormap
+            maps the *C* values to colors. Defaults to :rc:`image.cmap`.
+
+        norm : `~matplotlib.colors.Normalize`, optional
+            The Normalize instance scales the data values to the canonical
+            colormap range [0, 1] for mapping to colors. By default, the data
+            range is mapped to the colorbar range using linear scaling.
+
+        vmin, vmax : scalar, optional, default: None
+            The colorbar range. If *None*, suitable min/max values are
+            automatically chosen by the `~.Normalize` instance (defaults to
+            the respective min/max values of *C* in case of the default linear
+            scaling).
+
+        alpha : scalar, optional, default: None
+            The alpha blending value, between 0 (transparent) and 1 (opaque).
+
+        snap : bool, optional, default: False
+            Whether to snap the mesh to pixel boundaries.
+
+        Returns
+        -------
+        image : `.AxesImage` or `.PcolorImage` or `.QuadMesh`
+            The return type depends on the type of grid:
+
+            - `.AxesImage` for a regular rectangular grid.
+            - `.PcolorImage` for a non-regular rectangular grid.
+            - `.QuadMesh` for a non-rectangular grid.
+
+        Notes
+        -----
+        .. [notes section required to get data note injection right]
+        """
+
+        C = args[-1]
+        nr, nc = np.shape(C)[:2]
+        if len(args) == 1:
+            style = "image"
+            x = [0, nc]
+            y = [0, nr]
+        elif len(args) == 3:
+            x, y = args[:2]
+            x = np.asarray(x)
+            y = np.asarray(y)
+            if x.ndim == 1 and y.ndim == 1:
+                if x.size == 2 and y.size == 2:
+                    style = "image"
+                else:
+                    dx = np.diff(x)
+                    dy = np.diff(y)
+                    if (np.ptp(dx) < 0.01 * np.abs(dx.mean()) and
+                        np.ptp(dy) < 0.01 * np.abs(dy.mean())):
+                        style = "image"
+                    else:
+                        style = "pcolorimage"
+            elif x.ndim == 2 and y.ndim == 2:
+                style = "quadmesh"
+            else:
+                raise TypeError("arguments do not match valid signatures")
+        else:
+            raise TypeError("need 1 argument or 3 arguments")
+
+        if style == "quadmesh":
+            # data point in each cell is value at lower left corner
+            coords = np.stack([x, y], axis=-1)
+            if np.ndim(C) == 2:
+                qm_kwargs = {"array": np.ma.ravel(C)}
+            elif np.ndim(C) == 3:
+                qm_kwargs = {"color": np.ma.reshape(C, (-1, C.shape[-1]))}
+            else:
+                raise ValueError("C must be 2D or 3D")
+            collection = mcoll.QuadMesh(
+                nc, nr, coords, **qm_kwargs,
+                alpha=alpha, cmap=cmap, norm=norm,
+                antialiased=False, edgecolors="none")
+            self.add_collection(collection, autolim=False)
+            xl, xr, yb, yt = x.min(), x.max(), y.min(), y.max()
+            ret = collection
+
+        else:  # It's one of the two image styles.
+            extent = xl, xr, yb, yt = x[0], x[-1], y[0], y[-1]
+            if style == "image":
+                im = mimage.AxesImage(
+                    self, cmap, norm,
+                    data=C, alpha=alpha, extent=extent,
+                    interpolation='nearest', origin='lower',
+                    **kwargs)
+            elif style == "pcolorimage":
+                im = mimage.PcolorImage(
+                    self, x, y, C,
+                    cmap=cmap, norm=norm, alpha=alpha, extent=extent,
+                    **kwargs)
+            self.add_image(im)
+            ret = im
+
+        if vmin is not None or vmax is not None:
+            ret.set_clim(vmin, vmax)
+        elif np.ndim(C) == 2:  # C.ndim == 3 is RGB(A) so doesn't need scaling.
+            ret.autoscale_None()
+        if ret.get_clip_path() is None:
+            # image does not already have clipping set, clip to axes patch
+            ret.set_clip_path(self.patch)
+
+        ret.sticky_edges.x[:] = [xl, xr]
+        ret.sticky_edges.y[:] = [yb, yt]
+        self.update_datalim(np.array([[xl, yb], [xr, yt]]))
+        self._request_autoscale_view(tight=True)
+        return ret
+
+    @_preprocess_data()
+    def contour(self, *args, **kwargs):
+        kwargs['filled'] = False
+        contours = mcontour.QuadContourSet(self, *args, **kwargs)
+        self._request_autoscale_view()
+        return contours
+    contour.__doc__ = mcontour.QuadContourSet._contour_doc
+
+    @_preprocess_data()
+    def contourf(self, *args, **kwargs):
+        kwargs['filled'] = True
+        contours = mcontour.QuadContourSet(self, *args, **kwargs)
+        self._request_autoscale_view()
+        return contours
+    contourf.__doc__ = mcontour.QuadContourSet._contour_doc
+
+    def clabel(self, CS, *args, **kwargs):
+        return CS.clabel(*args, **kwargs)
+    clabel.__doc__ = mcontour.ContourSet.clabel.__doc__
+
+    #### Data analysis
+
+    @_preprocess_data(replace_names=["x", 'weights'], label_namer="x")
+    def hist(self, x, bins=None, range=None, density=False, weights=None,
+             cumulative=False, bottom=None, histtype='bar', align='mid',
+             orientation='vertical', rwidth=None, log=False,
+             color=None, label=None, stacked=False, **kwargs):
+        """
+        Plot a histogram.
+
+        Compute and draw the histogram of *x*.  The return value is a tuple
+        (*n*, *bins*, *patches*) or ([*n0*, *n1*, ...], *bins*, [*patches0*,
+        *patches1*,...]) if the input contains multiple data.  See the
+        documentation of the *weights* parameter to draw a histogram of
+        already-binned data.
+
+        Multiple data can be provided via *x* as a list of datasets
+        of potentially different length ([*x0*, *x1*, ...]), or as
+        a 2-D ndarray in which each column is a dataset.  Note that
+        the ndarray form is transposed relative to the list form.
+
+        Masked arrays are not supported.
+
+        The *bins*, *range*, *weights*, and *density* parameters behave as in
+        `numpy.histogram`.
+
+        Parameters
+        ----------
+        x : (n,) array or sequence of (n,) arrays
+            Input values, this takes either a single array or a sequence of
+            arrays which are not required to be of the same length.
+
+        bins : int or sequence or str, optional
+            If *bins* is an integer, it defines the number of equal-width bins
+            in the range.
+
+            If *bins* is a sequence, it defines the bin edges, including the
+            left edge of the first bin and the right edge of the last bin;
+            in this case, bins may be unequally spaced.  All but the last
+            (righthand-most) bin is half-open.  In other words, if *bins* is::
+
+                [1, 2, 3, 4]
+
+            then the first bin is ``[1, 2)`` (including 1, but excluding 2) and
+            the second ``[2, 3)``.  The last bin, however, is ``[3, 4]``, which
+            *includes* 4.
+
+            If *bins* is a string, it is one of the binning strategies
+            supported by `numpy.histogram_bin_edges`: 'auto', 'fd', 'doane',
+            'scott', 'stone', 'rice', 'sturges', or 'sqrt'.
+
+            The default is :rc:`hist.bins`.
+
+        range : tuple or None, optional
+            The lower and upper range of the bins. Lower and upper outliers
+            are ignored. If not provided, *range* is ``(x.min(), x.max())``.
+            Range has no effect if *bins* is a sequence.
+
+            If *bins* is a sequence or *range* is specified, autoscaling
+            is based on the specified bin range instead of the
+            range of x.
+
+            Default is ``None``
+
+        density : bool, optional
+            If ``True``, the first element of the return tuple will
+            be the counts normalized to form a probability density, i.e.,
+            the area (or integral) under the histogram will sum to 1.
+            This is achieved by dividing the count by the number of
+            observations times the bin width and not dividing by the total
+            number of observations. If *stacked* is also ``True``, the sum of
+            the histograms is normalized to 1.
+
+            Default is ``False``.
+
+        weights : (n, ) array-like or None, optional
+            An array of weights, of the same shape as *x*.  Each value in *x*
+            only contributes its associated weight towards the bin count
+            (instead of 1).  If *normed* or *density* is ``True``,
+            the weights are normalized, so that the integral of the density
+            over the range remains 1.
+
+            Default is ``None``.
+
+            This parameter can be used to draw a histogram of data that has
+            already been binned, e.g. using `np.histogram` (by treating each
+            bin as a single point with a weight equal to its count) ::
+
+                counts, bins = np.histogram(data)
+                plt.hist(bins[:-1], bins, weights=counts)
+
+            (or you may alternatively use `~.bar()`).
+
+        cumulative : bool or -1, optional
+            If ``True``, then a histogram is computed where each bin gives the
+            counts in that bin plus all bins for smaller values. The last bin
+            gives the total number of datapoints.
+
+            If *density* is also ``True`` then the histogram is normalized such
+            that the last bin equals 1.
+
+            If *cumulative* is a number less than 0 (e.g., -1), the direction
+            of accumulation is reversed.  In this case, if *density* is also
+            ``True``, then the histogram is normalized such that the first bin
+            equals 1.
+
+        bottom : array-like, scalar, or None, default: None
+            Location of the bottom of each bin, ie. bins are drawn from
+            ``bottom`` to ``bottom + hist(x, bins)`` If a scalar, the bottom
+            of each bin is shifted by the same amount. If an array, each bin
+            is shifted independently and the length of bottom must match the
+            number of bins. If None, defaults to 0.
+
+        histtype : {'bar', 'barstacked', 'step',  'stepfilled'}, optional
+            The type of histogram to draw.
+
+            - 'bar' is a traditional bar-type histogram.  If multiple data
+              are given the bars are arranged side by side.
+            - 'barstacked' is a bar-type histogram where multiple
+              data are stacked on top of each other.
+            - 'step' generates a lineplot that is by default unfilled.
+            - 'stepfilled' generates a lineplot that is by default filled.
+
+            Default is 'bar'
+
+        align : {'left', 'mid', 'right'}, optional
+            Controls how the histogram is plotted.
+
+            - 'left': bars are centered on the left bin edges.
+            - 'mid': bars are centered between the bin edges.
+            - 'right': bars are centered on the right bin edges.
+
+            Default is 'mid'
+
+        orientation : {'horizontal', 'vertical'}, optional
+            If 'horizontal', `~matplotlib.pyplot.barh` will be used for
+            bar-type histograms and the *bottom* kwarg will be the left edges.
+
+        rwidth : scalar or None, optional
+            The relative width of the bars as a fraction of the bin width.  If
+            ``None``, automatically compute the width.
+
+            Ignored if *histtype* is 'step' or 'stepfilled'.
+
+            Default is ``None``
+
+        log : bool, optional
+            If ``True``, the histogram axis will be set to a log scale. If
+            *log* is ``True`` and *x* is a 1D array, empty bins will be
+            filtered out and only the non-empty ``(n, bins, patches)``
+            will be returned.
+
+            Default is ``False``
+
+        color : color or array-like of colors or None, optional
+            Color or sequence of colors, one per dataset.  Default (``None``)
+            uses the standard line color sequence.
+
+            Default is ``None``
+
+        label : str or None, optional
+            String, or sequence of strings to match multiple datasets.  Bar
+            charts yield multiple patches per dataset, but only the first gets
+            the label, so that the legend command will work as expected.
+
+            default is ``None``
+
+        stacked : bool, optional
+            If ``True``, multiple data are stacked on top of each other If
+            ``False`` multiple data are arranged side by side if histtype is
+            'bar' or on top of each other if histtype is 'step'
+
+            Default is ``False``
+
+        Returns
+        -------
+        n : array or list of arrays
+            The values of the histogram bins. See *density* and *weights* for a
+            description of the possible semantics.  If input *x* is an array,
+            then this is an array of length *nbins*. If input is a sequence of
+            arrays ``[data1, data2, ...]``, then this is a list of arrays with
+            the values of the histograms for each of the arrays in the same
+            order.  The dtype of the array *n* (or of its element arrays) will
+            always be float even if no weighting or normalization is used.
+
+        bins : array
+            The edges of the bins. Length nbins + 1 (nbins left edges and right
+            edge of last bin).  Always a single array even when multiple data
+            sets are passed in.
+
+        patches : list or list of lists
+            Silent list of individual patches used to create the histogram
+            or list of such list if multiple input datasets.
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.patches.Patch` properties
+
+        See also
+        --------
+        hist2d : 2D histograms
+
+        """
+        # Avoid shadowing the builtin.
+        bin_range = range
+        from builtins import range
+
+        if np.isscalar(x):
+            x = [x]
+
+        if bins is None:
+            bins = rcParams['hist.bins']
+
+        # Validate string inputs here to avoid cluttering subsequent code.
+        cbook._check_in_list(['bar', 'barstacked', 'step', 'stepfilled'],
+                             histtype=histtype)
+        cbook._check_in_list(['left', 'mid', 'right'], align=align)
+        cbook._check_in_list(['horizontal', 'vertical'],
+                             orientation=orientation)
+
+        if histtype == 'barstacked' and not stacked:
+            stacked = True
+
+        # basic input validation
+        input_empty = np.size(x) == 0
+        # Massage 'x' for processing.
+        x = cbook._reshape_2D(x, 'x')
+        nx = len(x)  # number of datasets
+
+        # Process unit information
+        # Unit conversion is done individually on each dataset
+        self._process_unit_info(xdata=x[0], kwargs=kwargs)
+        x = [self.convert_xunits(xi) for xi in x]
+
+        if bin_range is not None:
+            bin_range = self.convert_xunits(bin_range)
+
+        if not cbook.is_scalar_or_string(bins):
+            bins = self.convert_xunits(bins)
+
+        # We need to do to 'weights' what was done to 'x'
+        if weights is not None:
+            w = cbook._reshape_2D(weights, 'weights')
+        else:
+            w = [None] * nx
+
+        if len(w) != nx:
+            raise ValueError('weights should have the same shape as x')
+
+        for xi, wi in zip(x, w):
+            if wi is not None and len(wi) != len(xi):
+                raise ValueError(
+                    'weights should have the same shape as x')
+
+        if color is None:
+            color = [self._get_lines.get_next_color() for i in range(nx)]
+        else:
+            color = mcolors.to_rgba_array(color)
+            if len(color) != nx:
+                error_message = (
+                    "color kwarg must have one color per data set. %d data "
+                    "sets and %d colors were provided" % (nx, len(color)))
+                raise ValueError(error_message)
+
+        hist_kwargs = dict()
+
+        # if the bin_range is not given, compute without nan numpy
+        # does not do this for us when guessing the range (but will
+        # happily ignore nans when computing the histogram).
+        if bin_range is None:
+            xmin = np.inf
+            xmax = -np.inf
+            for xi in x:
+                if len(xi):
+                    # python's min/max ignore nan,
+                    # np.minnan returns nan for all nan input
+                    xmin = min(xmin, np.nanmin(xi))
+                    xmax = max(xmax, np.nanmax(xi))
+            # make sure we have seen at least one non-nan and finite
+            # value before we reset the bin range
+            if not np.isnan([xmin, xmax]).any() and not (xmin > xmax):
+                bin_range = (xmin, xmax)
+
+        # If bins are not specified either explicitly or via range,
+        # we need to figure out the range required for all datasets,
+        # and supply that to np.histogram.
+        if not input_empty and len(x) > 1:
+            if weights is not None:
+                _w = np.concatenate(w)
+            else:
+                _w = None
+
+            bins = _histogram_bin_edges(np.concatenate(x), bins, bin_range, _w)
+        else:
+            hist_kwargs['range'] = bin_range
+
+        density = bool(density)
+        if density and not stacked:
+            hist_kwargs['density'] = density
+
+        # List to store all the top coordinates of the histograms
+        tops = []  # Will have shape (n_datasets, n_bins).
+        # Loop through datasets
+        for i in range(nx):
+            # this will automatically overwrite bins,
+            # so that each histogram uses the same bins
+            m, bins = np.histogram(x[i], bins, weights=w[i], **hist_kwargs)
+            tops.append(m)
+        tops = np.array(tops, float)  # causes problems later if it's an int
+        if stacked:
+            tops = tops.cumsum(axis=0)
+            # If a stacked density plot, normalize so the area of all the
+            # stacked histograms together is 1
+            if density:
+                tops = (tops / np.diff(bins)) / tops[-1].sum()
+        if cumulative:
+            slc = slice(None)
+            if isinstance(cumulative, Number) and cumulative < 0:
+                slc = slice(None, None, -1)
+            if density:
+                tops = (tops * np.diff(bins))[:, slc].cumsum(axis=1)[:, slc]
+            else:
+                tops = tops[:, slc].cumsum(axis=1)[:, slc]
+
+        patches = []
+
+        # Save autoscale state for later restoration; turn autoscaling
+        # off so we can do it all a single time at the end, instead
+        # of having it done by bar or fill and then having to be redone.
+        _saved_autoscalex = self.get_autoscalex_on()
+        _saved_autoscaley = self.get_autoscaley_on()
+        self.set_autoscalex_on(False)
+        self.set_autoscaley_on(False)
+
+        if histtype.startswith('bar'):
+
+            totwidth = np.diff(bins)
+
+            if rwidth is not None:
+                dr = np.clip(rwidth, 0, 1)
+            elif (len(tops) > 1 and
+                  ((not stacked) or rcParams['_internal.classic_mode'])):
+                dr = 0.8
+            else:
+                dr = 1.0
+
+            if histtype == 'bar' and not stacked:
+                width = dr * totwidth / nx
+                dw = width
+                boffset = -0.5 * dr * totwidth * (1 - 1 / nx)
+            elif histtype == 'barstacked' or stacked:
+                width = dr * totwidth
+                boffset, dw = 0.0, 0.0
+
+            if align == 'mid':
+                boffset += 0.5 * totwidth
+            elif align == 'right':
+                boffset += totwidth
+
+            if orientation == 'horizontal':
+                _barfunc = self.barh
+                bottom_kwarg = 'left'
+            else:  # orientation == 'vertical'
+                _barfunc = self.bar
+                bottom_kwarg = 'bottom'
+
+            for m, c in zip(tops, color):
+                if bottom is None:
+                    bottom = np.zeros(len(m))
+                if stacked:
+                    height = m - bottom
+                else:
+                    height = m
+                patch = _barfunc(bins[:-1]+boffset, height, width,
+                                 align='center', log=log,
+                                 color=c, **{bottom_kwarg: bottom})
+                patches.append(patch)
+                if stacked:
+                    bottom[:] = m
+                boffset += dw
+
+        elif histtype.startswith('step'):
+            # these define the perimeter of the polygon
+            x = np.zeros(4 * len(bins) - 3)
+            y = np.zeros(4 * len(bins) - 3)
+
+            x[0:2*len(bins)-1:2], x[1:2*len(bins)-1:2] = bins, bins[:-1]
+            x[2*len(bins)-1:] = x[1:2*len(bins)-1][::-1]
+
+            if bottom is None:
+                bottom = np.zeros(len(bins) - 1)
+
+            y[1:2*len(bins)-1:2], y[2:2*len(bins):2] = bottom, bottom
+            y[2*len(bins)-1:] = y[1:2*len(bins)-1][::-1]
+
+            if log:
+                if orientation == 'horizontal':
+                    self.set_xscale('log', nonposx='clip')
+                else:  # orientation == 'vertical'
+                    self.set_yscale('log', nonposy='clip')
+
+            if align == 'left':
+                x -= 0.5*(bins[1]-bins[0])
+            elif align == 'right':
+                x += 0.5*(bins[1]-bins[0])
+
+            # If fill kwarg is set, it will be passed to the patch collection,
+            # overriding this
+            fill = (histtype == 'stepfilled')
+
+            xvals, yvals = [], []
+            for m in tops:
+                if stacked:
+                    # starting point for drawing polygon
+                    y[0] = y[1]
+                    # top of the previous polygon becomes the bottom
+                    y[2*len(bins)-1:] = y[1:2*len(bins)-1][::-1]
+                # set the top of this polygon
+                y[1:2*len(bins)-1:2], y[2:2*len(bins):2] = (m + bottom,
+                                                            m + bottom)
+                if orientation == 'horizontal':
+                    xvals.append(y.copy())
+                    yvals.append(x.copy())
+                else:
+                    xvals.append(x.copy())
+                    yvals.append(y.copy())
+
+            # stepfill is closed, step is not
+            split = -1 if fill else 2 * len(bins)
+            # add patches in reverse order so that when stacking,
+            # items lower in the stack are plotted on top of
+            # items higher in the stack
+            for x, y, c in reversed(list(zip(xvals, yvals, color))):
+                patches.append(self.fill(
+                    x[:split], y[:split],
+                    closed=True if fill else None,
+                    facecolor=c,
+                    edgecolor=None if fill else c,
+                    fill=fill if fill else None))
+            for patch_list in patches:
+                for patch in patch_list:
+                    if orientation == 'vertical':
+                        patch.sticky_edges.y.append(0)
+                    elif orientation == 'horizontal':
+                        patch.sticky_edges.x.append(0)
+
+            # we return patches, so put it back in the expected order
+            patches.reverse()
+
+        self.set_autoscalex_on(_saved_autoscalex)
+        self.set_autoscaley_on(_saved_autoscaley)
+        self._request_autoscale_view()
+
+        if label is None:
+            labels = [None]
+        elif isinstance(label, str):
+            labels = [label]
+        elif not np.iterable(label):
+            labels = [str(label)]
+        else:
+            labels = [str(lab) for lab in label]
+
+        for patch, lbl in itertools.zip_longest(patches, labels):
+            if patch:
+                p = patch[0]
+                p.update(kwargs)
+                if lbl is not None:
+                    p.set_label(lbl)
+
+                for p in patch[1:]:
+                    p.update(kwargs)
+                    p.set_label('_nolegend_')
+
+        if nx == 1:
+            return tops[0], bins, cbook.silent_list('Patch', patches[0])
+        else:
+            return tops, bins, cbook.silent_list('Lists of Patches', patches)
+
+    @_preprocess_data(replace_names=["x", "y", "weights"])
+    @cbook._rename_parameter("3.1", "normed", "density")
+    def hist2d(self, x, y, bins=10, range=None, density=False, weights=None,
+               cmin=None, cmax=None, **kwargs):
+        """
+        Make a 2D histogram plot.
+
+        Parameters
+        ----------
+        x, y : array-like, shape (n, )
+            Input values
+
+        bins : None or int or [int, int] or array-like or [array, array]
+
+            The bin specification:
+
+            - If int, the number of bins for the two dimensions
+              (nx=ny=bins).
+            - If ``[int, int]``, the number of bins in each dimension
+              (nx, ny = bins).
+            - If array-like, the bin edges for the two dimensions
+              (x_edges=y_edges=bins).
+            - If ``[array, array]``, the bin edges in each dimension
+              (x_edges, y_edges = bins).
+
+            The default value is 10.
+
+        range : array-like shape(2, 2), optional, default: None
+            The leftmost and rightmost edges of the bins along each dimension
+            (if not specified explicitly in the bins parameters): ``[[xmin,
+            xmax], [ymin, ymax]]``. All values outside of this range will be
+            considered outliers and not tallied in the histogram.
+
+        density : bool, optional, default: False
+            Normalize histogram.  *normed* is a deprecated synonym for this
+            parameter.
+
+        weights : array-like, shape (n, ), optional, default: None
+            An array of values w_i weighing each sample (x_i, y_i).
+
+        cmin : scalar, optional, default: None
+            All bins that has count less than cmin will not be displayed (set
+            to NaN before passing to imshow) and these count values in the
+            return value count histogram will also be set to nan upon return.
+
+        cmax : scalar, optional, default: None
+            All bins that has count more than cmax will not be displayed (set
+            to NaN before passing to imshow) and these count values in the
+            return value count histogram will also be set to nan upon return.
+
+        Returns
+        -------
+        h : 2D array
+            The bi-dimensional histogram of samples x and y. Values in x are
+            histogrammed along the first dimension and values in y are
+            histogrammed along the second dimension.
+        xedges : 1D array
+            The bin edges along the x axis.
+        yedges : 1D array
+            The bin edges along the y axis.
+        image : `~.matplotlib.collections.QuadMesh`
+
+        Other Parameters
+        ----------------
+        cmap : Colormap or str, optional
+            A `.colors.Colormap` instance.  If not set, use rc settings.
+
+        norm : Normalize, optional
+            A `.colors.Normalize` instance is used to
+            scale luminance data to ``[0, 1]``. If not set, defaults to
+            `.colors.Normalize()`.
+
+        vmin/vmax : None or scalar, optional
+            Arguments passed to the `~.colors.Normalize` instance.
+
+        alpha : ``0 <= scalar <= 1`` or ``None``, optional
+            The alpha blending value.
+
+        See also
+        --------
+        hist : 1D histogram plotting
+
+        Notes
+        -----
+        - Currently ``hist2d`` calculates its own axis limits, and any limits
+          previously set are ignored.
+        - Rendering the histogram with a logarithmic color scale is
+          accomplished by passing a `.colors.LogNorm` instance to the *norm*
+          keyword argument. Likewise, power-law normalization (similar
+          in effect to gamma correction) can be accomplished with
+          `.colors.PowerNorm`.
+        """
+
+        h, xedges, yedges = np.histogram2d(x, y, bins=bins, range=range,
+                                           normed=density, weights=weights)
+
+        if cmin is not None:
+            h[h < cmin] = None
+        if cmax is not None:
+            h[h > cmax] = None
+
+        pc = self.pcolormesh(xedges, yedges, h.T, **kwargs)
+        self.set_xlim(xedges[0], xedges[-1])
+        self.set_ylim(yedges[0], yedges[-1])
+
+        return h, xedges, yedges, pc
+
+    @_preprocess_data(replace_names=["x"])
+    @docstring.dedent_interpd
+    def psd(self, x, NFFT=None, Fs=None, Fc=None, detrend=None,
+            window=None, noverlap=None, pad_to=None,
+            sides=None, scale_by_freq=None, return_line=None, **kwargs):
+        r"""
+        Plot the power spectral density.
+
+        The power spectral density :math:`P_{xx}` by Welch's average
+        periodogram method.  The vector *x* is divided into *NFFT* length
+        segments.  Each segment is detrended by function *detrend* and
+        windowed by function *window*.  *noverlap* gives the length of
+        the overlap between segments.  The :math:`|\mathrm{fft}(i)|^2`
+        of each segment :math:`i` are averaged to compute :math:`P_{xx}`,
+        with a scaling to correct for power loss due to windowing.
+
+        If len(*x*) < *NFFT*, it will be zero padded to *NFFT*.
+
+        Parameters
+        ----------
+        x : 1-D array or sequence
+            Array or sequence containing the data
+
+        %(Spectral)s
+
+        %(PSD)s
+
+        noverlap : int
+            The number of points of overlap between segments.
+            The default value is 0 (no overlap).
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+        return_line : bool
+            Whether to include the line object plotted in the returned values.
+            Default is False.
+
+        Returns
+        -------
+        Pxx : 1-D array
+            The values for the power spectrum `P_{xx}` before scaling
+            (real valued).
+
+        freqs : 1-D array
+            The frequencies corresponding to the elements in *Pxx*.
+
+        line : `~matplotlib.lines.Line2D`
+            The line created by this function.
+            Only returned if *return_line* is True.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        See Also
+        --------
+        :func:`specgram`
+            :func:`specgram` differs in the default overlap; in not returning
+            the mean of the segment periodograms; in returning the times of the
+            segments; and in plotting a colormap instead of a line.
+
+        :func:`magnitude_spectrum`
+            :func:`magnitude_spectrum` plots the magnitude spectrum.
+
+        :func:`csd`
+            :func:`csd` plots the spectral density between two signals.
+
+        Notes
+        -----
+        For plotting, the power is plotted as
+        :math:`10\log_{10}(P_{xx})` for decibels, though *Pxx* itself
+        is returned.
+
+        References
+        ----------
+        Bendat & Piersol -- Random Data: Analysis and Measurement Procedures,
+        John Wiley & Sons (1986)
+        """
+        if Fc is None:
+            Fc = 0
+
+        pxx, freqs = mlab.psd(x=x, NFFT=NFFT, Fs=Fs, detrend=detrend,
+                              window=window, noverlap=noverlap, pad_to=pad_to,
+                              sides=sides, scale_by_freq=scale_by_freq)
+        freqs += Fc
+
+        if scale_by_freq in (None, True):
+            psd_units = 'dB/Hz'
+        else:
+            psd_units = 'dB'
+
+        line = self.plot(freqs, 10 * np.log10(pxx), **kwargs)
+        self.set_xlabel('Frequency')
+        self.set_ylabel('Power Spectral Density (%s)' % psd_units)
+        self.grid(True)
+        vmin, vmax = self.viewLim.intervaly
+        intv = vmax - vmin
+        logi = int(np.log10(intv))
+        if logi == 0:
+            logi = .1
+        step = 10 * logi
+        ticks = np.arange(math.floor(vmin), math.ceil(vmax) + 1, step)
+        self.set_yticks(ticks)
+
+        if return_line is None or not return_line:
+            return pxx, freqs
+        else:
+            return pxx, freqs, line
+
+    @_preprocess_data(replace_names=["x", "y"], label_namer="y")
+    @docstring.dedent_interpd
+    def csd(self, x, y, NFFT=None, Fs=None, Fc=None, detrend=None,
+            window=None, noverlap=None, pad_to=None,
+            sides=None, scale_by_freq=None, return_line=None, **kwargs):
+        r"""
+        Plot the cross-spectral density.
+
+        The cross spectral density :math:`P_{xy}` by Welch's average
+        periodogram method.  The vectors *x* and *y* are divided into
+        *NFFT* length segments.  Each segment is detrended by function
+        *detrend* and windowed by function *window*.  *noverlap* gives
+        the length of the overlap between segments.  The product of
+        the direct FFTs of *x* and *y* are averaged over each segment
+        to compute :math:`P_{xy}`, with a scaling to correct for power
+        loss due to windowing.
+
+        If len(*x*) < *NFFT* or len(*y*) < *NFFT*, they will be zero
+        padded to *NFFT*.
+
+        Parameters
+        ----------
+        x, y : 1-D arrays or sequences
+            Arrays or sequences containing the data.
+
+        %(Spectral)s
+
+        %(PSD)s
+
+        noverlap : int
+            The number of points of overlap between segments.
+            The default value is 0 (no overlap).
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+        return_line : bool
+            Whether to include the line object plotted in the returned values.
+            Default is False.
+
+        Returns
+        -------
+        Pxy : 1-D array
+            The values for the cross spectrum `P_{xy}` before scaling
+            (complex valued).
+
+        freqs : 1-D array
+            The frequencies corresponding to the elements in *Pxy*.
+
+        line : `~matplotlib.lines.Line2D`
+            The line created by this function.
+            Only returned if *return_line* is True.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        See Also
+        --------
+        :func:`psd`
+            :func:`psd` is the equivalent to setting y=x.
+
+        Notes
+        -----
+        For plotting, the power is plotted as
+        :math:`10 \log_{10}(P_{xy})` for decibels, though `P_{xy}` itself
+        is returned.
+
+        References
+        ----------
+        Bendat & Piersol -- Random Data: Analysis and Measurement Procedures,
+        John Wiley & Sons (1986)
+        """
+        if Fc is None:
+            Fc = 0
+
+        pxy, freqs = mlab.csd(x=x, y=y, NFFT=NFFT, Fs=Fs, detrend=detrend,
+                              window=window, noverlap=noverlap, pad_to=pad_to,
+                              sides=sides, scale_by_freq=scale_by_freq)
+        # pxy is complex
+        freqs += Fc
+
+        line = self.plot(freqs, 10 * np.log10(np.abs(pxy)), **kwargs)
+        self.set_xlabel('Frequency')
+        self.set_ylabel('Cross Spectrum Magnitude (dB)')
+        self.grid(True)
+        vmin, vmax = self.viewLim.intervaly
+
+        intv = vmax - vmin
+        step = 10 * int(np.log10(intv))
+
+        ticks = np.arange(math.floor(vmin), math.ceil(vmax) + 1, step)
+        self.set_yticks(ticks)
+
+        if return_line is None or not return_line:
+            return pxy, freqs
+        else:
+            return pxy, freqs, line
+
+    @_preprocess_data(replace_names=["x"])
+    @docstring.dedent_interpd
+    def magnitude_spectrum(self, x, Fs=None, Fc=None, window=None,
+                           pad_to=None, sides=None, scale=None,
+                           **kwargs):
+        """
+        Plot the magnitude spectrum.
+
+        Compute the magnitude spectrum of *x*.  Data is padded to a
+        length of *pad_to* and the windowing function *window* is applied to
+        the signal.
+
+        Parameters
+        ----------
+        x : 1-D array or sequence
+            Array or sequence containing the data.
+
+        %(Spectral)s
+
+        %(Single_Spectrum)s
+
+        scale : {'default', 'linear', 'dB'}
+            The scaling of the values in the *spec*.  'linear' is no scaling.
+            'dB' returns the values in dB scale, i.e., the dB amplitude
+            (20 * log10). 'default' is 'linear'.
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+        Returns
+        -------
+        spectrum : 1-D array
+            The values for the magnitude spectrum before scaling (real valued).
+
+        freqs : 1-D array
+            The frequencies corresponding to the elements in *spectrum*.
+
+        line : `~matplotlib.lines.Line2D`
+            The line created by this function.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        See Also
+        --------
+        :func:`psd`
+            :func:`psd` plots the power spectral density.`.
+
+        :func:`angle_spectrum`
+            :func:`angle_spectrum` plots the angles of the corresponding
+            frequencies.
+
+        :func:`phase_spectrum`
+            :func:`phase_spectrum` plots the phase (unwrapped angle) of the
+            corresponding frequencies.
+
+        :func:`specgram`
+            :func:`specgram` can plot the magnitude spectrum of segments within
+            the signal in a colormap.
+
+        """
+        if Fc is None:
+            Fc = 0
+
+        if scale is None or scale == 'default':
+            scale = 'linear'
+
+        spec, freqs = mlab.magnitude_spectrum(x=x, Fs=Fs, window=window,
+                                              pad_to=pad_to, sides=sides)
+        freqs += Fc
+
+        if scale == 'linear':
+            Z = spec
+            yunits = 'energy'
+        elif scale == 'dB':
+            Z = 20. * np.log10(spec)
+            yunits = 'dB'
+        else:
+            raise ValueError('Unknown scale %s', scale)
+
+        lines = self.plot(freqs, Z, **kwargs)
+        self.set_xlabel('Frequency')
+        self.set_ylabel('Magnitude (%s)' % yunits)
+
+        return spec, freqs, lines[0]
+
+    @_preprocess_data(replace_names=["x"])
+    @docstring.dedent_interpd
+    def angle_spectrum(self, x, Fs=None, Fc=None, window=None,
+                       pad_to=None, sides=None, **kwargs):
+        """
+        Plot the angle spectrum.
+
+        Compute the angle spectrum (wrapped phase spectrum) of *x*.
+        Data is padded to a length of *pad_to* and the windowing function
+        *window* is applied to the signal.
+
+        Parameters
+        ----------
+        x : 1-D array or sequence
+            Array or sequence containing the data.
+
+        %(Spectral)s
+
+        %(Single_Spectrum)s
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+        Returns
+        -------
+        spectrum : 1-D array
+            The values for the angle spectrum in radians (real valued).
+
+        freqs : 1-D array
+            The frequencies corresponding to the elements in *spectrum*.
+
+        line : `~matplotlib.lines.Line2D`
+            The line created by this function.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        See Also
+        --------
+        :func:`magnitude_spectrum`
+            :func:`angle_spectrum` plots the magnitudes of the corresponding
+            frequencies.
+
+        :func:`phase_spectrum`
+            :func:`phase_spectrum` plots the unwrapped version of this
+            function.
+
+        :func:`specgram`
+            :func:`specgram` can plot the angle spectrum of segments within the
+            signal in a colormap.
+
+        """
+        if Fc is None:
+            Fc = 0
+
+        spec, freqs = mlab.angle_spectrum(x=x, Fs=Fs, window=window,
+                                          pad_to=pad_to, sides=sides)
+        freqs += Fc
+
+        lines = self.plot(freqs, spec, **kwargs)
+        self.set_xlabel('Frequency')
+        self.set_ylabel('Angle (radians)')
+
+        return spec, freqs, lines[0]
+
+    @_preprocess_data(replace_names=["x"])
+    @docstring.dedent_interpd
+    def phase_spectrum(self, x, Fs=None, Fc=None, window=None,
+                       pad_to=None, sides=None, **kwargs):
+        """
+        Plot the phase spectrum.
+
+        Compute the phase spectrum (unwrapped angle spectrum) of *x*.
+        Data is padded to a length of *pad_to* and the windowing function
+        *window* is applied to the signal.
+
+        Parameters
+        ----------
+        x : 1-D array or sequence
+            Array or sequence containing the data
+
+        %(Spectral)s
+
+        %(Single_Spectrum)s
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+        Returns
+        -------
+        spectrum : 1-D array
+            The values for the phase spectrum in radians (real valued).
+
+        freqs : 1-D array
+            The frequencies corresponding to the elements in *spectrum*.
+
+        line : `~matplotlib.lines.Line2D`
+            The line created by this function.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        See Also
+        --------
+        :func:`magnitude_spectrum`
+            :func:`magnitude_spectrum` plots the magnitudes of the
+            corresponding frequencies.
+
+        :func:`angle_spectrum`
+            :func:`angle_spectrum` plots the wrapped version of this function.
+
+        :func:`specgram`
+            :func:`specgram` can plot the phase spectrum of segments within the
+            signal in a colormap.
+
+        """
+        if Fc is None:
+            Fc = 0
+
+        spec, freqs = mlab.phase_spectrum(x=x, Fs=Fs, window=window,
+                                          pad_to=pad_to, sides=sides)
+        freqs += Fc
+
+        lines = self.plot(freqs, spec, **kwargs)
+        self.set_xlabel('Frequency')
+        self.set_ylabel('Phase (radians)')
+
+        return spec, freqs, lines[0]
+
+    @_preprocess_data(replace_names=["x", "y"])
+    @docstring.dedent_interpd
+    def cohere(self, x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
+               window=mlab.window_hanning, noverlap=0, pad_to=None,
+               sides='default', scale_by_freq=None, **kwargs):
+        r"""
+        Plot the coherence between *x* and *y*.
+
+        Plot the coherence between *x* and *y*.  Coherence is the
+        normalized cross spectral density:
+
+        .. math::
+
+          C_{xy} = \frac{|P_{xy}|^2}{P_{xx}P_{yy}}
+
+        Parameters
+        ----------
+        %(Spectral)s
+
+        %(PSD)s
+
+        noverlap : int
+            The number of points of overlap between blocks.  The
+            default value is 0 (no overlap).
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+
+        Returns
+        -------
+        Cxy : 1-D array
+            The coherence vector.
+
+        freqs : 1-D array
+            The frequencies for the elements in *Cxy*.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Keyword arguments control the `.Line2D` properties:
+
+            %(_Line2D_docstr)s
+
+        References
+        ----------
+        Bendat & Piersol -- Random Data: Analysis and Measurement Procedures,
+        John Wiley & Sons (1986)
+        """
+        cxy, freqs = mlab.cohere(x=x, y=y, NFFT=NFFT, Fs=Fs, detrend=detrend,
+                                 window=window, noverlap=noverlap,
+                                 scale_by_freq=scale_by_freq)
+        freqs += Fc
+
+        self.plot(freqs, cxy, **kwargs)
+        self.set_xlabel('Frequency')
+        self.set_ylabel('Coherence')
+        self.grid(True)
+
+        return cxy, freqs
+
+    @_preprocess_data(replace_names=["x"])
+    @docstring.dedent_interpd
+    def specgram(self, x, NFFT=None, Fs=None, Fc=None, detrend=None,
+                 window=None, noverlap=None,
+                 cmap=None, xextent=None, pad_to=None, sides=None,
+                 scale_by_freq=None, mode=None, scale=None,
+                 vmin=None, vmax=None, **kwargs):
+        """
+        Plot a spectrogram.
+
+        Compute and plot a spectrogram of data in *x*.  Data are split into
+        *NFFT* length segments and the spectrum of each section is
+        computed.  The windowing function *window* is applied to each
+        segment, and the amount of overlap of each segment is
+        specified with *noverlap*. The spectrogram is plotted as a colormap
+        (using imshow).
+
+        Parameters
+        ----------
+        x : 1-D array or sequence
+            Array or sequence containing the data.
+
+        %(Spectral)s
+
+        %(PSD)s
+
+        mode : {'default', 'psd', 'magnitude', 'angle', 'phase'}
+            What sort of spectrum to use.  Default is 'psd', which takes the
+            power spectral density.  'magnitude' returns the magnitude
+            spectrum.  'angle' returns the phase spectrum without unwrapping.
+            'phase' returns the phase spectrum with unwrapping.
+
+        noverlap : int
+            The number of points of overlap between blocks.  The
+            default value is 128.
+
+        scale : {'default', 'linear', 'dB'}
+            The scaling of the values in the *spec*.  'linear' is no scaling.
+            'dB' returns the values in dB scale.  When *mode* is 'psd',
+            this is dB power (10 * log10).  Otherwise this is dB amplitude
+            (20 * log10). 'default' is 'dB' if *mode* is 'psd' or
+            'magnitude' and 'linear' otherwise.  This must be 'linear'
+            if *mode* is 'angle' or 'phase'.
+
+        Fc : int
+            The center frequency of *x* (defaults to 0), which offsets
+            the x extents of the plot to reflect the frequency range used
+            when a signal is acquired and then filtered and downsampled to
+            baseband.
+
+        cmap
+            A :class:`matplotlib.colors.Colormap` instance; if *None*, use
+            default determined by rc
+
+        xextent : *None* or (xmin, xmax)
+            The image extent along the x-axis. The default sets *xmin* to the
+            left border of the first bin (*spectrum* column) and *xmax* to the
+            right border of the last bin. Note that for *noverlap>0* the width
+            of the bins is smaller than those of the segments.
+
+        **kwargs
+            Additional keyword arguments are passed on to imshow which makes
+            the specgram image.
+
+        Returns
+        -------
+        spectrum : 2-D array
+            Columns are the periodograms of successive segments.
+
+        freqs : 1-D array
+            The frequencies corresponding to the rows in *spectrum*.
+
+        t : 1-D array
+            The times corresponding to midpoints of segments (i.e., the columns
+            in *spectrum*).
+
+        im : instance of class :class:`~matplotlib.image.AxesImage`
+            The image created by imshow containing the spectrogram
+
+        See Also
+        --------
+        :func:`psd`
+            :func:`psd` differs in the default overlap; in returning the mean
+            of the segment periodograms; in not returning times; and in
+            generating a line plot instead of colormap.
+
+        :func:`magnitude_spectrum`
+            A single spectrum, similar to having a single segment when *mode*
+            is 'magnitude'. Plots a line instead of a colormap.
+
+        :func:`angle_spectrum`
+            A single spectrum, similar to having a single segment when *mode*
+            is 'angle'. Plots a line instead of a colormap.
+
+        :func:`phase_spectrum`
+            A single spectrum, similar to having a single segment when *mode*
+            is 'phase'. Plots a line instead of a colormap.
+
+        Notes
+        -----
+        The parameters *detrend* and *scale_by_freq* do only apply when *mode*
+        is set to 'psd'.
+        """
+        if NFFT is None:
+            NFFT = 256  # same default as in mlab.specgram()
+        if Fc is None:
+            Fc = 0  # same default as in mlab._spectral_helper()
+        if noverlap is None:
+            noverlap = 128  # same default as in mlab.specgram()
+
+        if mode == 'complex':
+            raise ValueError('Cannot plot a complex specgram')
+
+        if scale is None or scale == 'default':
+            if mode in ['angle', 'phase']:
+                scale = 'linear'
+            else:
+                scale = 'dB'
+        elif mode in ['angle', 'phase'] and scale == 'dB':
+            raise ValueError('Cannot use dB scale with angle or phase mode')
+
+        spec, freqs, t = mlab.specgram(x=x, NFFT=NFFT, Fs=Fs,
+                                       detrend=detrend, window=window,
+                                       noverlap=noverlap, pad_to=pad_to,
+                                       sides=sides,
+                                       scale_by_freq=scale_by_freq,
+                                       mode=mode)
+
+        if scale == 'linear':
+            Z = spec
+        elif scale == 'dB':
+            if mode is None or mode == 'default' or mode == 'psd':
+                Z = 10. * np.log10(spec)
+            else:
+                Z = 20. * np.log10(spec)
+        else:
+            raise ValueError('Unknown scale %s', scale)
+
+        Z = np.flipud(Z)
+
+        if xextent is None:
+            # padding is needed for first and last segment:
+            pad_xextent = (NFFT-noverlap) / Fs / 2
+            xextent = np.min(t) - pad_xextent, np.max(t) + pad_xextent
+        xmin, xmax = xextent
+        freqs += Fc
+        extent = xmin, xmax, freqs[0], freqs[-1]
+        im = self.imshow(Z, cmap, extent=extent, vmin=vmin, vmax=vmax,
+                         **kwargs)
+        self.axis('auto')
+
+        return spec, freqs, t, im
+
+    @docstring.dedent_interpd
+    def spy(self, Z, precision=0, marker=None, markersize=None,
+            aspect='equal', origin="upper", **kwargs):
+        """
+        Plot the sparsity pattern of a 2D array.
+
+        This visualizes the non-zero values of the array.
+
+        Two plotting styles are available: image and marker. Both
+        are available for full arrays, but only the marker style
+        works for `scipy.sparse.spmatrix` instances.
+
+        **Image style**
+
+        If *marker* and *markersize* are *None*, `~.Axes.imshow` is used. Any
+        extra remaining keyword arguments are passed to this method.
+
+        **Marker style**
+
+        If *Z* is a `scipy.sparse.spmatrix` or *marker* or *markersize* are
+        *None*, a `.Line2D` object will be returned with the value of marker
+        determining the marker type, and any remaining keyword arguments
+        passed to `~.Axes.plot`.
+
+        Parameters
+        ----------
+        Z : array-like (M, N)
+            The array to be plotted.
+
+        precision : float or 'present', optional, default: 0
+            If *precision* is 0, any non-zero value will be plotted. Otherwise,
+            values of :math:`|Z| > precision` will be plotted.
+
+            For :class:`scipy.sparse.spmatrix` instances, you can also
+            pass 'present'. In this case any value present in the array
+            will be plotted, even if it is identically zero.
+
+        origin : {'upper', 'lower'}, optional
+            Place the [0, 0] index of the array in the upper left or lower left
+            corner of the axes. The convention 'upper' is typically used for
+            matrices and images.
+            If not given, :rc:`image.origin` is used, defaulting to 'upper'.
+
+
+        aspect : {'equal', 'auto', None} or float, optional
+            Controls the aspect ratio of the axes. The aspect is of particular
+            relevance for images since it may distort the image, i.e. pixel
+            will not be square.
+
+            This parameter is a shortcut for explicitly calling
+            `.Axes.set_aspect`. See there for further details.
+
+            - 'equal': Ensures an aspect ratio of 1. Pixels will be square.
+            - 'auto': The axes is kept fixed and the aspect is adjusted so
+              that the data fit in the axes. In general, this will result in
+              non-square pixels.
+            - *None*: Use :rc:`image.aspect`.
+
+            Default: 'equal'
+
+        Returns
+        -------
+        ret : `~matplotlib.image.AxesImage` or `.Line2D`
+            The return type depends on the plotting style (see above).
+
+        Other Parameters
+        ----------------
+        **kwargs
+            The supported additional parameters depend on the plotting style.
+
+            For the image style, you can pass the following additional
+            parameters of `~.Axes.imshow`:
+
+            - *cmap*
+            - *alpha*
+            - *url*
+            - any `.Artist` properties (passed on to the `.AxesImage`)
+
+            For the marker style, you can pass any `.Line2D` property except
+            for *linestyle*:
+
+            %(_Line2D_docstr)s
+        """
+        if marker is None and markersize is None and hasattr(Z, 'tocoo'):
+            marker = 's'
+        if marker is None and markersize is None:
+            Z = np.asarray(Z)
+            mask = np.abs(Z) > precision
+
+            if 'cmap' not in kwargs:
+                kwargs['cmap'] = mcolors.ListedColormap(['w', 'k'],
+                                                        name='binary')
+            if 'interpolation' in kwargs:
+                raise TypeError(
+                    "spy() got an unexpected keyword argument 'interpolation'")
+            ret = self.imshow(mask, interpolation='nearest', aspect=aspect,
+                              origin=origin, **kwargs)
+        else:
+            if hasattr(Z, 'tocoo'):
+                c = Z.tocoo()
+                if precision == 'present':
+                    y = c.row
+                    x = c.col
+                else:
+                    nonzero = np.abs(c.data) > precision
+                    y = c.row[nonzero]
+                    x = c.col[nonzero]
+            else:
+                Z = np.asarray(Z)
+                nonzero = np.abs(Z) > precision
+                y, x = np.nonzero(nonzero)
+            if marker is None:
+                marker = 's'
+            if markersize is None:
+                markersize = 10
+            if 'linestyle' in kwargs:
+                raise TypeError(
+                    "spy() got an unexpected keyword argument 'linestyle'")
+            marks = mlines.Line2D(x, y, linestyle='None',
+                         marker=marker, markersize=markersize, **kwargs)
+            self.add_line(marks)
+            nr, nc = Z.shape
+            self.set_xlim(-0.5, nc - 0.5)
+            self.set_ylim(nr - 0.5, -0.5)
+            self.set_aspect(aspect)
+            ret = marks
+        self.title.set_y(1.05)
+        self.xaxis.tick_top()
+        self.xaxis.set_ticks_position('both')
+        self.xaxis.set_major_locator(mticker.MaxNLocator(nbins=9,
+                                                 steps=[1, 2, 5, 10],
+                                                 integer=True))
+        self.yaxis.set_major_locator(mticker.MaxNLocator(nbins=9,
+                                                 steps=[1, 2, 5, 10],
+                                                 integer=True))
+        return ret
+
+    def matshow(self, Z, **kwargs):
+        """
+        Plot the values of a 2D matrix or array as color-coded image.
+
+        The matrix will be shown the way it would be printed, with the first
+        row at the top.  Row and column numbering is zero-based.
+
+        Parameters
+        ----------
+        Z : array-like(M, N)
+            The matrix to be displayed.
+
+        Returns
+        -------
+        image : `~matplotlib.image.AxesImage`
+
+        Other Parameters
+        ----------------
+        **kwargs : `~matplotlib.axes.Axes.imshow` arguments
+
+        See Also
+        --------
+        imshow : More general function to plot data on a 2D regular raster.
+
+        Notes
+        -----
+        This is just a convenience function wrapping `.imshow` to set useful
+        defaults for displaying a matrix. In particular:
+
+        - Set ``origin='upper'``.
+        - Set ``interpolation='nearest'``.
+        - Set ``aspect='equal'``.
+        - Ticks are placed to the left and above.
+        - Ticks are formatted to show integer indices.
+
+        """
+        Z = np.asanyarray(Z)
+        kw = {'origin': 'upper',
+              'interpolation': 'nearest',
+              'aspect': 'equal',          # (already the imshow default)
+              **kwargs}
+        im = self.imshow(Z, **kw)
+        self.title.set_y(1.05)
+        self.xaxis.tick_top()
+        self.xaxis.set_ticks_position('both')
+        self.xaxis.set_major_locator(mticker.MaxNLocator(nbins=9,
+                                                 steps=[1, 2, 5, 10],
+                                                 integer=True))
+        self.yaxis.set_major_locator(mticker.MaxNLocator(nbins=9,
+                                                 steps=[1, 2, 5, 10],
+                                                 integer=True))
+        return im
+
+    @_preprocess_data(replace_names=["dataset"])
+    def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
+                   showmeans=False, showextrema=True, showmedians=False,
+                   quantiles=None, points=100, bw_method=None):
+        """
+        Make a violin plot.
+
+        Make a violin plot for each column of *dataset* or each vector in
+        sequence *dataset*.  Each filled area extends to represent the
+        entire data range, with optional lines at the mean, the median,
+        the minimum, the maximum, and user-specified quantiles.
+
+        Parameters
+        ----------
+        dataset : Array or a sequence of vectors.
+          The input data.
+
+        positions : array-like, default = [1, 2, ..., n]
+          Sets the positions of the violins. The ticks and limits are
+          automatically set to match the positions.
+
+        vert : bool, default = True.
+          If true, creates a vertical violin plot.
+          Otherwise, creates a horizontal violin plot.
+
+        widths : array-like, default = 0.5
+          Either a scalar or a vector that sets the maximal width of
+          each violin. The default is 0.5, which uses about half of the
+          available horizontal space.
+
+        showmeans : bool, default = False
+          If `True`, will toggle rendering of the means.
+
+        showextrema : bool, default = True
+          If `True`, will toggle rendering of the extrema.
+
+        showmedians : bool, default = False
+          If `True`, will toggle rendering of the medians.
+
+        quantiles : array-like, default = None
+          If not None, set a list of floats in interval [0, 1] for each violin,
+          which stands for the quantiles that will be rendered for that
+          violin.
+
+        points : scalar, default = 100
+          Defines the number of points to evaluate each of the
+          gaussian kernel density estimations at.
+
+        bw_method : str, scalar or callable, optional
+          The method used to calculate the estimator bandwidth.  This can be
+          'scott', 'silverman', a scalar constant or a callable.  If a
+          scalar, this will be used directly as `kde.factor`.  If a
+          callable, it should take a `GaussianKDE` instance as its only
+          parameter and return a scalar. If None (default), 'scott' is used.
+
+        Returns
+        -------
+        result : dict
+          A dictionary mapping each component of the violinplot to a
+          list of the corresponding collection instances created. The
+          dictionary has the following keys:
+
+          - ``bodies``: A list of the `~.collections.PolyCollection`
+            instances containing the filled area of each violin.
+
+          - ``cmeans``: A `~.collections.LineCollection` instance that marks
+            the mean values of each of the violin's distribution.
+
+          - ``cmins``: A `~.collections.LineCollection` instance that marks
+            the bottom of each violin's distribution.
+
+          - ``cmaxes``: A `~.collections.LineCollection` instance that marks
+            the top of each violin's distribution.
+
+          - ``cbars``: A `~.collections.LineCollection` instance that marks
+            the centers of each violin's distribution.
+
+          - ``cmedians``: A `~.collections.LineCollection` instance that
+            marks the median values of each of the violin's distribution.
+
+          - ``cquantiles``: A `~.collections.LineCollection` instance created
+            to identify the quantile values of each of the violin's
+            distribution.
+
+        """
+
+        def _kde_method(X, coords):
+            # fallback gracefully if the vector contains only one value
+            if np.all(X[0] == X):
+                return (X[0] == coords).astype(float)
+            kde = mlab.GaussianKDE(X, bw_method)
+            return kde.evaluate(coords)
+
+        vpstats = cbook.violin_stats(dataset, _kde_method, points=points,
+                                     quantiles=quantiles)
+        return self.violin(vpstats, positions=positions, vert=vert,
+                           widths=widths, showmeans=showmeans,
+                           showextrema=showextrema, showmedians=showmedians)
+
+    def violin(self, vpstats, positions=None, vert=True, widths=0.5,
+               showmeans=False, showextrema=True, showmedians=False):
+        """Drawing function for violin plots.
+
+        Draw a violin plot for each column of *vpstats*. Each filled area
+        extends to represent the entire data range, with optional lines at the
+        mean, the median, the minimum, the maximum, and the quantiles values.
+
+        Parameters
+        ----------
+        vpstats : list of dicts
+          A list of dictionaries containing stats for each violin plot.
+          Required keys are:
+
+          - ``coords``: A list of scalars containing the coordinates that
+            the violin's kernel density estimate were evaluated at.
+
+          - ``vals``: A list of scalars containing the values of the
+            kernel density estimate at each of the coordinates given
+            in *coords*.
+
+          - ``mean``: The mean value for this violin's dataset.
+
+          - ``median``: The median value for this violin's dataset.
+
+          - ``min``: The minimum value for this violin's dataset.
+
+          - ``max``: The maximum value for this violin's dataset.
+
+          Optional keys are:
+
+          - ``quantiles``: A list of scalars containing the quantile values
+            for this violin's dataset.
+
+        positions : array-like, default = [1, 2, ..., n]
+          Sets the positions of the violins. The ticks and limits are
+          automatically set to match the positions.
+
+        vert : bool, default = True.
+          If true, plots the violins vertically.
+          Otherwise, plots the violins horizontally.
+
+        widths : array-like, default = 0.5
+          Either a scalar or a vector that sets the maximal width of
+          each violin. The default is 0.5, which uses about half of the
+          available horizontal space.
+
+        showmeans : bool, default = False
+          If true, will toggle rendering of the means.
+
+        showextrema : bool, default = True
+          If true, will toggle rendering of the extrema.
+
+        showmedians : bool, default = False
+          If true, will toggle rendering of the medians.
+
+        Returns
+        -------
+        result : dict
+          A dictionary mapping each component of the violinplot to a
+          list of the corresponding collection instances created. The
+          dictionary has the following keys:
+
+          - ``bodies``: A list of the `~.collections.PolyCollection`
+            instances containing the filled area of each violin.
+
+          - ``cmeans``: A `~.collections.LineCollection` instance that marks
+            the mean values of each of the violin's distribution.
+
+          - ``cmins``: A `~.collections.LineCollection` instance that marks
+            the bottom of each violin's distribution.
+
+          - ``cmaxes``: A `~.collections.LineCollection` instance that marks
+            the top of each violin's distribution.
+
+          - ``cbars``: A `~.collections.LineCollection` instance that marks
+            the centers of each violin's distribution.
+
+          - ``cmedians``: A `~.collections.LineCollection` instance that
+            marks the median values of each of the violin's distribution.
+
+          - ``cquantiles``: A `~.collections.LineCollection` instance created
+            to identify the quantiles values of each of the violin's
+            distribution.
+
+        """
+
+        # Statistical quantities to be plotted on the violins
+        means = []
+        mins = []
+        maxes = []
+        medians = []
+        quantiles = np.asarray([])
+
+        # Collections to be returned
+        artists = {}
+
+        N = len(vpstats)
+        datashape_message = ("List of violinplot statistics and `{0}` "
+                             "values must have the same length")
+
+        # Validate positions
+        if positions is None:
+            positions = range(1, N + 1)
+        elif len(positions) != N:
+            raise ValueError(datashape_message.format("positions"))
+
+        # Validate widths
+        if np.isscalar(widths):
+            widths = [widths] * N
+        elif len(widths) != N:
+            raise ValueError(datashape_message.format("widths"))
+
+        # Calculate ranges for statistics lines
+        pmins = -0.25 * np.array(widths) + positions
+        pmaxes = 0.25 * np.array(widths) + positions
+
+        # Check whether we are rendering vertically or horizontally
+        if vert:
+            fill = self.fill_betweenx
+            perp_lines = self.hlines
+            par_lines = self.vlines
+        else:
+            fill = self.fill_between
+            perp_lines = self.vlines
+            par_lines = self.hlines
+
+        if rcParams['_internal.classic_mode']:
+            fillcolor = 'y'
+            edgecolor = 'r'
+        else:
+            fillcolor = edgecolor = self._get_lines.get_next_color()
+
+        # Render violins
+        bodies = []
+        for stats, pos, width in zip(vpstats, positions, widths):
+            # The 0.5 factor reflects the fact that we plot from v-p to
+            # v+p
+            vals = np.array(stats['vals'])
+            vals = 0.5 * width * vals / vals.max()
+            bodies += [fill(stats['coords'],
+                            -vals + pos,
+                            vals + pos,
+                            facecolor=fillcolor,
+                            alpha=0.3)]
+            means.append(stats['mean'])
+            mins.append(stats['min'])
+            maxes.append(stats['max'])
+            medians.append(stats['median'])
+            q = stats.get('quantiles')
+            if q is not None:
+                # If exist key quantiles, assume it's a list of floats
+                quantiles = np.concatenate((quantiles, q))
+        artists['bodies'] = bodies
+
+        # Render means
+        if showmeans:
+            artists['cmeans'] = perp_lines(means, pmins, pmaxes,
+                                           colors=edgecolor)
+
+        # Render extrema
+        if showextrema:
+            artists['cmaxes'] = perp_lines(maxes, pmins, pmaxes,
+                                           colors=edgecolor)
+            artists['cmins'] = perp_lines(mins, pmins, pmaxes,
+                                          colors=edgecolor)
+            artists['cbars'] = par_lines(positions, mins, maxes,
+                                         colors=edgecolor)
+
+        # Render medians
+        if showmedians:
+            artists['cmedians'] = perp_lines(medians,
+                                             pmins,
+                                             pmaxes,
+                                             colors=edgecolor)
+
+        # Render quantile values
+        if quantiles.size > 0:
+            # Recalculate ranges for statistics lines for quantiles.
+            # ppmins are the left end of quantiles lines
+            ppmins = np.asarray([])
+            # pmaxes are the right end of quantiles lines
+            ppmaxs = np.asarray([])
+            for stats, cmin, cmax in zip(vpstats, pmins, pmaxes):
+                q = stats.get('quantiles')
+                if q is not None:
+                    ppmins = np.concatenate((ppmins, [cmin] * np.size(q)))
+                    ppmaxs = np.concatenate((ppmaxs, [cmax] * np.size(q)))
+            # Start rendering
+            artists['cquantiles'] = perp_lines(quantiles, ppmins, ppmaxs,
+                                                 colors=edgecolor)
+
+        return artists
+
+    # Methods that are entirely implemented in other modules.
+
+    table = mtable.table
+
+    # args can by either Y or y1, y2, ... and all should be replaced
+    stackplot = _preprocess_data()(mstack.stackplot)
+
+    streamplot = _preprocess_data(
+        replace_names=["x", "y", "u", "v", "start_points"])(mstream.streamplot)
+
+    tricontour = mtri.tricontour
+    tricontourf = mtri.tricontourf
+    tripcolor = mtri.tripcolor
+    triplot = mtri.triplot

+ 4441 - 0
venv/lib/python3.8/site-packages/matplotlib/axes/_base.py

@@ -0,0 +1,4441 @@
+from collections import OrderedDict
+import itertools
+import logging
+import math
+from numbers import Real
+from operator import attrgetter
+import types
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import cbook, rcParams
+from matplotlib.cbook import _OrderedSet, _check_1d, index_of
+from matplotlib import docstring
+import matplotlib.colors as mcolors
+import matplotlib.lines as mlines
+import matplotlib.patches as mpatches
+import matplotlib.artist as martist
+import matplotlib.transforms as mtransforms
+import matplotlib.ticker as mticker
+import matplotlib.axis as maxis
+import matplotlib.spines as mspines
+import matplotlib.font_manager as font_manager
+import matplotlib.text as mtext
+import matplotlib.image as mimage
+from matplotlib.rcsetup import cycler, validate_axisbelow
+
+_log = logging.getLogger(__name__)
+
+
+def _process_plot_format(fmt):
+    """
+    Convert a MATLAB style color/line style format string to a (*linestyle*,
+    *marker*, *color*) tuple.
+
+    Example format strings include:
+
+    * 'ko': black circles
+    * '.b': blue dots
+    * 'r--': red dashed lines
+    * 'C2--': the third color in the color cycle, dashed lines
+
+    See Also
+    --------
+    matplotlib.Line2D.lineStyles, matplotlib.colors.cnames
+        All possible styles and color format strings.
+    """
+
+    linestyle = None
+    marker = None
+    color = None
+
+    # Is fmt just a colorspec?
+    try:
+        color = mcolors.to_rgba(fmt)
+
+        # We need to differentiate grayscale '1.0' from tri_down marker '1'
+        try:
+            fmtint = str(int(fmt))
+        except ValueError:
+            return linestyle, marker, color  # Yes
+        else:
+            if fmt != fmtint:
+                # user definitely doesn't want tri_down marker
+                return linestyle, marker, color  # Yes
+            else:
+                # ignore converted color
+                color = None
+    except ValueError:
+        pass  # No, not just a color.
+
+    i = 0
+    while i < len(fmt):
+        c = fmt[i]
+        if fmt[i:i+2] in mlines.lineStyles:  # First, the two-char styles.
+            if linestyle is not None:
+                raise ValueError(
+                    'Illegal format string "%s"; two linestyle symbols' % fmt)
+            linestyle = fmt[i:i+2]
+            i += 2
+        elif c in mlines.lineStyles:
+            if linestyle is not None:
+                raise ValueError(
+                    'Illegal format string "%s"; two linestyle symbols' % fmt)
+            linestyle = c
+            i += 1
+        elif c in mlines.lineMarkers:
+            if marker is not None:
+                raise ValueError(
+                    'Illegal format string "%s"; two marker symbols' % fmt)
+            marker = c
+            i += 1
+        elif c in mcolors.get_named_colors_mapping():
+            if color is not None:
+                raise ValueError(
+                    'Illegal format string "%s"; two color symbols' % fmt)
+            color = c
+            i += 1
+        elif c == 'C' and i < len(fmt) - 1:
+            color_cycle_number = int(fmt[i + 1])
+            color = mcolors.to_rgba("C{}".format(color_cycle_number))
+            i += 2
+        else:
+            raise ValueError(
+                'Unrecognized character %c in format string' % c)
+
+    if linestyle is None and marker is None:
+        linestyle = rcParams['lines.linestyle']
+    if linestyle is None:
+        linestyle = 'None'
+    if marker is None:
+        marker = 'None'
+
+    return linestyle, marker, color
+
+
+class _process_plot_var_args:
+    """
+    Process variable length arguments to the plot command, so that
+    plot commands like the following are supported::
+
+      plot(t, s)
+      plot(t1, s1, t2, s2)
+      plot(t1, s1, 'ko', t2, s2)
+      plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3)
+
+    an arbitrary number of *x*, *y*, *fmt* are allowed
+    """
+    def __init__(self, axes, command='plot'):
+        self.axes = axes
+        self.command = command
+        self.set_prop_cycle()
+
+    def __getstate__(self):
+        # note: it is not possible to pickle a generator (and thus a cycler).
+        return {'axes': self.axes, 'command': self.command}
+
+    def __setstate__(self, state):
+        self.__dict__ = state.copy()
+        self.set_prop_cycle()
+
+    def set_prop_cycle(self, *args, **kwargs):
+        # Can't do `args == (None,)` as that crashes cycler.
+        if not (args or kwargs) or (len(args) == 1 and args[0] is None):
+            prop_cycler = rcParams['axes.prop_cycle']
+        else:
+            prop_cycler = cycler(*args, **kwargs)
+
+        self.prop_cycler = itertools.cycle(prop_cycler)
+        # This should make a copy
+        self._prop_keys = prop_cycler.keys
+
+    def __call__(self, *args, **kwargs):
+        self.axes._process_unit_info(kwargs=kwargs)
+
+        for pos_only in "xy":
+            if pos_only in kwargs:
+                raise TypeError("{} got an unexpected keyword argument {!r}"
+                                .format(self.command, pos_only))
+
+        if not args:
+            return
+
+        # Process the 'data' kwarg.
+        data = kwargs.pop("data", None)
+        if data is not None:
+            replaced = [mpl._replacer(data, arg) for arg in args]
+            if len(args) == 1:
+                label_namer_idx = 0
+            elif len(args) == 2:  # Can be x, y or y, c.
+                # Figure out what the second argument is.
+                # 1) If the second argument cannot be a format shorthand, the
+                #    second argument is the label_namer.
+                # 2) Otherwise (it could have been a format shorthand),
+                #    a) if we did perform a substitution, emit a warning, and
+                #       use it as label_namer.
+                #    b) otherwise, it is indeed a format shorthand; use the
+                #       first argument as label_namer.
+                try:
+                    _process_plot_format(args[1])
+                except ValueError:  # case 1)
+                    label_namer_idx = 1
+                else:
+                    if replaced[1] is not args[1]:  # case 2a)
+                        cbook._warn_external(
+                            f"Second argument {args[1]!r} is ambiguous: could "
+                            f"be a format string but is in 'data'; using as "
+                            f"data.  If it was intended as data, set the "
+                            f"format string to an empty string to suppress "
+                            f"this warning.  If it was intended as a format "
+                            f"string, explicitly pass the x-values as well.  "
+                            f"Alternatively, rename the entry in 'data'.",
+                            RuntimeWarning)
+                        label_namer_idx = 1
+                    else:  # case 2b)
+                        label_namer_idx = 0
+            elif len(args) == 3:
+                label_namer_idx = 1
+            else:
+                raise ValueError(
+                    "Using arbitrary long args with data is not supported due "
+                    "to ambiguity of arguments; use multiple plotting calls "
+                    "instead")
+            if kwargs.get("label") is None:
+                kwargs["label"] = mpl._label_from_arg(
+                    replaced[label_namer_idx], args[label_namer_idx])
+            args = replaced
+
+        # Repeatedly grab (x, y) or (x, y, format) from the front of args and
+        # massage them into arguments to plot() or fill().
+        while args:
+            this, args = args[:2], args[2:]
+            if args and isinstance(args[0], str):
+                this += args[0],
+                args = args[1:]
+            yield from self._plot_args(this, kwargs)
+
+    def get_next_color(self):
+        """Return the next color in the cycle."""
+        if 'color' not in self._prop_keys:
+            return 'k'
+        return next(self.prop_cycler)['color']
+
+    def _getdefaults(self, ignore, kw):
+        """
+        If some keys in the property cycle (excluding those in the set
+        *ignore*) are absent or set to None in the dict *kw*, return a copy
+        of the next entry in the property cycle, excluding keys in *ignore*.
+        Otherwise, don't advance the property cycle, and return an empty dict.
+        """
+        prop_keys = self._prop_keys - ignore
+        if any(kw.get(k, None) is None for k in prop_keys):
+            # Need to copy this dictionary or else the next time around
+            # in the cycle, the dictionary could be missing entries.
+            default_dict = next(self.prop_cycler).copy()
+            for p in ignore:
+                default_dict.pop(p, None)
+        else:
+            default_dict = {}
+        return default_dict
+
+    def _setdefaults(self, defaults, kw):
+        """
+        Add to the dict *kw* the entries in the dict *default* that are absent
+        or set to None in *kw*.
+        """
+        for k in defaults:
+            if kw.get(k, None) is None:
+                kw[k] = defaults[k]
+
+    def _makeline(self, x, y, kw, kwargs):
+        kw = {**kw, **kwargs}  # Don't modify the original kw.
+        default_dict = self._getdefaults(set(), kw)
+        self._setdefaults(default_dict, kw)
+        seg = mlines.Line2D(x, y, **kw)
+        return seg
+
+    def _makefill(self, x, y, kw, kwargs):
+        # Polygon doesn't directly support unitized inputs.
+        x = self.axes.convert_xunits(x)
+        y = self.axes.convert_yunits(y)
+
+        kw = kw.copy()  # Don't modify the original kw.
+        kwargs = kwargs.copy()
+
+        # Ignore 'marker'-related properties as they aren't Polygon
+        # properties, but they are Line2D properties, and so they are
+        # likely to appear in the default cycler construction.
+        # This is done here to the defaults dictionary as opposed to the
+        # other two dictionaries because we do want to capture when a
+        # *user* explicitly specifies a marker which should be an error.
+        # We also want to prevent advancing the cycler if there are no
+        # defaults needed after ignoring the given properties.
+        ignores = {'marker', 'markersize', 'markeredgecolor',
+                   'markerfacecolor', 'markeredgewidth'}
+        # Also ignore anything provided by *kwargs*.
+        for k, v in kwargs.items():
+            if v is not None:
+                ignores.add(k)
+
+        # Only using the first dictionary to use as basis
+        # for getting defaults for back-compat reasons.
+        # Doing it with both seems to mess things up in
+        # various places (probably due to logic bugs elsewhere).
+        default_dict = self._getdefaults(ignores, kw)
+        self._setdefaults(default_dict, kw)
+
+        # Looks like we don't want "color" to be interpreted to
+        # mean both facecolor and edgecolor for some reason.
+        # So the "kw" dictionary is thrown out, and only its
+        # 'color' value is kept and translated as a 'facecolor'.
+        # This design should probably be revisited as it increases
+        # complexity.
+        facecolor = kw.get('color', None)
+
+        # Throw out 'color' as it is now handled as a facecolor
+        default_dict.pop('color', None)
+
+        # To get other properties set from the cycler
+        # modify the kwargs dictionary.
+        self._setdefaults(default_dict, kwargs)
+
+        seg = mpatches.Polygon(np.column_stack((x, y)),
+                               facecolor=facecolor,
+                               fill=kwargs.get('fill', True),
+                               closed=kw['closed'])
+        seg.set(**kwargs)
+        return seg
+
+    def _plot_args(self, tup, kwargs):
+        if len(tup) > 1 and isinstance(tup[-1], str):
+            linestyle, marker, color = _process_plot_format(tup[-1])
+            tup = tup[:-1]
+        elif len(tup) == 3:
+            raise ValueError('third arg must be a format string')
+        else:
+            linestyle, marker, color = None, None, None
+
+        # Don't allow any None value; these would be up-converted to one
+        # element array of None which causes problems downstream.
+        if any(v is None for v in tup):
+            raise ValueError("x, y, and format string must not be None")
+
+        kw = {}
+        for k, v in zip(('linestyle', 'marker', 'color'),
+                        (linestyle, marker, color)):
+            if v is not None:
+                kw[k] = v
+
+        if len(tup) == 2:
+            x = _check_1d(tup[0])
+            y = _check_1d(tup[-1])
+        else:
+            x, y = index_of(tup[-1])
+
+        if self.axes.xaxis is not None:
+            self.axes.xaxis.update_units(x)
+        if self.axes.yaxis is not None:
+            self.axes.yaxis.update_units(y)
+
+        if x.shape[0] != y.shape[0]:
+            raise ValueError(f"x and y must have same first dimension, but "
+                             f"have shapes {x.shape} and {y.shape}")
+        if x.ndim > 2 or y.ndim > 2:
+            raise ValueError(f"x and y can be no greater than 2-D, but have "
+                             f"shapes {x.shape} and {y.shape}")
+        if x.ndim == 1:
+            x = x[:, np.newaxis]
+        if y.ndim == 1:
+            y = y[:, np.newaxis]
+
+        if self.command == 'plot':
+            func = self._makeline
+        else:
+            kw['closed'] = kwargs.get('closed', True)
+            func = self._makefill
+
+        ncx, ncy = x.shape[1], y.shape[1]
+        if ncx > 1 and ncy > 1 and ncx != ncy:
+            cbook.warn_deprecated(
+                "2.2", message="cycling among columns of inputs with "
+                "non-matching shapes is deprecated.")
+        return [func(x[:, j % ncx], y[:, j % ncy], kw, kwargs)
+                for j in range(max(ncx, ncy))]
+
+
+class _AxesBase(martist.Artist):
+    name = "rectilinear"
+
+    _shared_x_axes = cbook.Grouper()
+    _shared_y_axes = cbook.Grouper()
+    _twinned_axes = cbook.Grouper()
+
+    def __str__(self):
+        return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format(
+            type(self).__name__, self._position.bounds)
+
+    def __init__(self, fig, rect,
+                 facecolor=None,  # defaults to rc axes.facecolor
+                 frameon=True,
+                 sharex=None,  # use Axes instance's xaxis info
+                 sharey=None,  # use Axes instance's yaxis info
+                 label='',
+                 xscale=None,
+                 yscale=None,
+                 **kwargs
+                 ):
+        """
+        Build an axes in a figure.
+
+        Parameters
+        ----------
+        fig : `~matplotlib.figure.Figure`
+            The axes is build in the `.Figure` *fig*.
+
+        rect : [left, bottom, width, height]
+            The axes is build in the rectangle *rect*. *rect* is in
+            `.Figure` coordinates.
+
+        sharex, sharey : `~.axes.Axes`, optional
+            The x or y `~.matplotlib.axis` is shared with the x or
+            y axis in the input `~.axes.Axes`.
+
+        frameon : bool, optional
+            True means that the axes frame is visible.
+
+        **kwargs
+            Other optional keyword arguments:
+
+            %(Axes)s
+
+        Returns
+        -------
+        axes : `~.axes.Axes`
+            The new `~.axes.Axes` object.
+        """
+
+        martist.Artist.__init__(self)
+        if isinstance(rect, mtransforms.Bbox):
+            self._position = rect
+        else:
+            self._position = mtransforms.Bbox.from_bounds(*rect)
+        if self._position.width < 0 or self._position.height < 0:
+            raise ValueError('Width and height specified must be non-negative')
+        self._originalPosition = self._position.frozen()
+        self.axes = self
+        self._aspect = 'auto'
+        self._adjustable = 'box'
+        self._anchor = 'C'
+        self._stale_viewlim_x = False
+        self._stale_viewlim_y = False
+        self._sharex = sharex
+        self._sharey = sharey
+        if sharex is not None:
+            self._shared_x_axes.join(self, sharex)
+        if sharey is not None:
+            self._shared_y_axes.join(self, sharey)
+        self.set_label(label)
+        self.set_figure(fig)
+
+        self.set_axes_locator(kwargs.get("axes_locator", None))
+
+        self.spines = self._gen_axes_spines()
+
+        # this call may differ for non-sep axes, e.g., polar
+        self._init_axis()
+        if facecolor is None:
+            facecolor = rcParams['axes.facecolor']
+        self._facecolor = facecolor
+        self._frameon = frameon
+        self.set_axisbelow(rcParams['axes.axisbelow'])
+
+        self._rasterization_zorder = None
+        self.cla()
+
+        # funcs used to format x and y - fall back on major formatters
+        self.fmt_xdata = None
+        self.fmt_ydata = None
+
+        self.set_navigate(True)
+        self.set_navigate_mode(None)
+
+        if xscale:
+            self.set_xscale(xscale)
+        if yscale:
+            self.set_yscale(yscale)
+
+        self.update(kwargs)
+
+        if self.xaxis is not None:
+            self._xcid = self.xaxis.callbacks.connect(
+                'units finalize', lambda: self._on_units_changed(scalex=True))
+
+        if self.yaxis is not None:
+            self._ycid = self.yaxis.callbacks.connect(
+                'units finalize', lambda: self._on_units_changed(scaley=True))
+
+        self.tick_params(
+            top=rcParams['xtick.top'] and rcParams['xtick.minor.top'],
+            bottom=rcParams['xtick.bottom'] and rcParams['xtick.minor.bottom'],
+            labeltop=(rcParams['xtick.labeltop'] and
+                      rcParams['xtick.minor.top']),
+            labelbottom=(rcParams['xtick.labelbottom'] and
+                         rcParams['xtick.minor.bottom']),
+            left=rcParams['ytick.left'] and rcParams['ytick.minor.left'],
+            right=rcParams['ytick.right'] and rcParams['ytick.minor.right'],
+            labelleft=(rcParams['ytick.labelleft'] and
+                       rcParams['ytick.minor.left']),
+            labelright=(rcParams['ytick.labelright'] and
+                        rcParams['ytick.minor.right']),
+            which='minor')
+
+        self.tick_params(
+            top=rcParams['xtick.top'] and rcParams['xtick.major.top'],
+            bottom=rcParams['xtick.bottom'] and rcParams['xtick.major.bottom'],
+            labeltop=(rcParams['xtick.labeltop'] and
+                      rcParams['xtick.major.top']),
+            labelbottom=(rcParams['xtick.labelbottom'] and
+                         rcParams['xtick.major.bottom']),
+            left=rcParams['ytick.left'] and rcParams['ytick.major.left'],
+            right=rcParams['ytick.right'] and rcParams['ytick.major.right'],
+            labelleft=(rcParams['ytick.labelleft'] and
+                       rcParams['ytick.major.left']),
+            labelright=(rcParams['ytick.labelright'] and
+                        rcParams['ytick.major.right']),
+            which='major')
+
+        self._layoutbox = None
+        self._poslayoutbox = None
+
+    def __getstate__(self):
+        # The renderer should be re-created by the figure, and then cached at
+        # that point.
+        state = super().__getstate__()
+        for key in ['_layoutbox', '_poslayoutbox']:
+            state[key] = None
+        # Prune the sharing & twinning info to only contain the current group.
+        for grouper_name in [
+                '_shared_x_axes', '_shared_y_axes', '_twinned_axes']:
+            grouper = getattr(self, grouper_name)
+            state[grouper_name] = (grouper.get_siblings(self)
+                                   if self in grouper else None)
+        return state
+
+    def __setstate__(self, state):
+        # Merge the grouping info back into the global groupers.
+        for grouper_name in [
+                '_shared_x_axes', '_shared_y_axes', '_twinned_axes']:
+            siblings = state.pop(grouper_name)
+            if siblings:
+                getattr(self, grouper_name).join(*siblings)
+        self.__dict__ = state
+        self._stale = True
+
+    def get_window_extent(self, *args, **kwargs):
+        """
+        Return the axes bounding box in display space; *args* and *kwargs*
+        are empty.
+
+        This bounding box does not include the spines, ticks, ticklables,
+        or other labels.  For a bounding box including these elements use
+        `~matplotlib.axes.Axes.get_tightbbox`.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.get_tightbbox
+        matplotlib.axis.Axis.get_tightbbox
+        matplotlib.spines.get_window_extent
+
+        """
+        return self.bbox
+
+    def _init_axis(self):
+        "move this out of __init__ because non-separable axes don't use it"
+        self.xaxis = maxis.XAxis(self)
+        self.spines['bottom'].register_axis(self.xaxis)
+        self.spines['top'].register_axis(self.xaxis)
+        self.yaxis = maxis.YAxis(self)
+        self.spines['left'].register_axis(self.yaxis)
+        self.spines['right'].register_axis(self.yaxis)
+        self._update_transScale()
+
+    def set_figure(self, fig):
+        """
+        Set the `.Figure` for this `.Axes`.
+
+        Parameters
+        ----------
+        fig : `.Figure`
+        """
+        martist.Artist.set_figure(self, fig)
+
+        self.bbox = mtransforms.TransformedBbox(self._position,
+                                                fig.transFigure)
+        # these will be updated later as data is added
+        self.dataLim = mtransforms.Bbox.null()
+        self._viewLim = mtransforms.Bbox.unit()
+        self.transScale = mtransforms.TransformWrapper(
+            mtransforms.IdentityTransform())
+
+        self._set_lim_and_transforms()
+
+    def _unstale_viewLim(self):
+        # We should arrange to store this information once per share-group
+        # instead of on every axis.
+        scalex = any(ax._stale_viewlim_x
+                     for ax in self._shared_x_axes.get_siblings(self))
+        scaley = any(ax._stale_viewlim_y
+                     for ax in self._shared_y_axes.get_siblings(self))
+        if scalex or scaley:
+            for ax in self._shared_x_axes.get_siblings(self):
+                ax._stale_viewlim_x = False
+            for ax in self._shared_y_axes.get_siblings(self):
+                ax._stale_viewlim_y = False
+            self.autoscale_view(scalex=scalex, scaley=scaley)
+
+    @property
+    def viewLim(self):
+        self._unstale_viewLim()
+        return self._viewLim
+
+    # API could be better, right now this is just to match the old calls to
+    # autoscale_view() after each plotting method.
+    def _request_autoscale_view(self, tight=None, scalex=True, scaley=True):
+        if tight is not None:
+            self._tight = tight
+        if scalex:
+            self._stale_viewlim_x = True  # Else keep old state.
+        if scaley:
+            self._stale_viewlim_y = True
+
+    def _set_lim_and_transforms(self):
+        """
+        Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*,
+        *transData*, *transLimits* and *transAxes* transformations.
+
+        .. note::
+
+            This method is primarily used by rectilinear projections of the
+            `~matplotlib.axes.Axes` class, and is meant to be overridden by
+            new kinds of projection axes that need different transformations
+            and limits. (See `~matplotlib.projections.polar.PolarAxes` for an
+            example.)
+        """
+        self.transAxes = mtransforms.BboxTransformTo(self.bbox)
+
+        # Transforms the x and y axis separately by a scale factor.
+        # It is assumed that this part will have non-linear components
+        # (e.g., for a log scale).
+        self.transScale = mtransforms.TransformWrapper(
+            mtransforms.IdentityTransform())
+
+        # An affine transformation on the data, generally to limit the
+        # range of the axes
+        self.transLimits = mtransforms.BboxTransformFrom(
+            mtransforms.TransformedBbox(self._viewLim, self.transScale))
+
+        # The parentheses are important for efficiency here -- they
+        # group the last two (which are usually affines) separately
+        # from the first (which, with log-scaling can be non-affine).
+        self.transData = self.transScale + (self.transLimits + self.transAxes)
+
+        self._xaxis_transform = mtransforms.blended_transform_factory(
+            self.transData, self.transAxes)
+        self._yaxis_transform = mtransforms.blended_transform_factory(
+            self.transAxes, self.transData)
+
+    def get_xaxis_transform(self, which='grid'):
+        """
+        Get the transformation used for drawing x-axis labels, ticks
+        and gridlines.  The x-direction is in data coordinates and the
+        y-direction is in axis coordinates.
+
+        .. note::
+
+            This transformation is primarily used by the
+            `~matplotlib.axis.Axis` class, and is meant to be
+            overridden by new kinds of projections that may need to
+            place axis elements in different locations.
+        """
+        if which == 'grid':
+            return self._xaxis_transform
+        elif which == 'tick1':
+            # for cartesian projection, this is bottom spine
+            return self.spines['bottom'].get_spine_transform()
+        elif which == 'tick2':
+            # for cartesian projection, this is top spine
+            return self.spines['top'].get_spine_transform()
+        else:
+            raise ValueError('unknown value for which')
+
+    def get_xaxis_text1_transform(self, pad_points):
+        """
+        Returns
+        -------
+        transform : Transform
+            The transform used for drawing x-axis labels, which will add
+            *pad_points* of padding (in points) between the axes and the label.
+            The x-direction is in data coordinates and the y-direction is in
+            axis corrdinates
+        valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
+            The text vertical alignment.
+        halign : {'center', 'left', 'right'}
+            The text horizontal alignment.
+
+        Notes
+        -----
+        This transformation is primarily used by the `~matplotlib.axis.Axis`
+        class, and is meant to be overridden by new kinds of projections that
+        may need to place axis elements in different locations.
+        """
+        labels_align = rcParams["xtick.alignment"]
+        return (self.get_xaxis_transform(which='tick1') +
+                mtransforms.ScaledTranslation(0, -1 * pad_points / 72,
+                                              self.figure.dpi_scale_trans),
+                "top", labels_align)
+
+    def get_xaxis_text2_transform(self, pad_points):
+        """
+        Returns
+        -------
+        transform : Transform
+            The transform used for drawing secondary x-axis labels, which will
+            add *pad_points* of padding (in points) between the axes and the
+            label.  The x-direction is in data coordinates and the y-direction
+            is in axis corrdinates
+        valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
+            The text vertical alignment.
+        halign : {'center', 'left', 'right'}
+            The text horizontal alignment.
+
+        Notes
+        -----
+        This transformation is primarily used by the `~matplotlib.axis.Axis`
+        class, and is meant to be overridden by new kinds of projections that
+        may need to place axis elements in different locations.
+        """
+        labels_align = rcParams["xtick.alignment"]
+        return (self.get_xaxis_transform(which='tick2') +
+                mtransforms.ScaledTranslation(0, pad_points / 72,
+                                              self.figure.dpi_scale_trans),
+                "bottom", labels_align)
+
+    def get_yaxis_transform(self, which='grid'):
+        """
+        Get the transformation used for drawing y-axis labels, ticks
+        and gridlines.  The x-direction is in axis coordinates and the
+        y-direction is in data coordinates.
+
+        .. note::
+
+            This transformation is primarily used by the
+            `~matplotlib.axis.Axis` class, and is meant to be
+            overridden by new kinds of projections that may need to
+            place axis elements in different locations.
+        """
+        if which == 'grid':
+            return self._yaxis_transform
+        elif which == 'tick1':
+            # for cartesian projection, this is bottom spine
+            return self.spines['left'].get_spine_transform()
+        elif which == 'tick2':
+            # for cartesian projection, this is top spine
+            return self.spines['right'].get_spine_transform()
+        else:
+            raise ValueError('unknown value for which')
+
+    def get_yaxis_text1_transform(self, pad_points):
+        """
+        Returns
+        -------
+        transform : Transform
+            The transform used for drawing y-axis labels, which will add
+            *pad_points* of padding (in points) between the axes and the label.
+            The x-direction is in axis coordinates and the y-direction is in
+            data corrdinates
+        valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
+            The text vertical alignment.
+        halign : {'center', 'left', 'right'}
+            The text horizontal alignment.
+
+        Notes
+        -----
+        This transformation is primarily used by the `~matplotlib.axis.Axis`
+        class, and is meant to be overridden by new kinds of projections that
+        may need to place axis elements in different locations.
+        """
+        labels_align = rcParams["ytick.alignment"]
+        return (self.get_yaxis_transform(which='tick1') +
+                mtransforms.ScaledTranslation(-1 * pad_points / 72, 0,
+                                              self.figure.dpi_scale_trans),
+                labels_align, "right")
+
+    def get_yaxis_text2_transform(self, pad_points):
+        """
+        Returns
+        -------
+        transform : Transform
+            The transform used for drawing secondart y-axis labels, which will
+            add *pad_points* of padding (in points) between the axes and the
+            label.  The x-direction is in axis coordinates and the y-direction
+            is in data corrdinates
+        valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
+            The text vertical alignment.
+        halign : {'center', 'left', 'right'}
+            The text horizontal alignment.
+
+        Notes
+        -----
+        This transformation is primarily used by the `~matplotlib.axis.Axis`
+        class, and is meant to be overridden by new kinds of projections that
+        may need to place axis elements in different locations.
+        """
+        labels_align = rcParams["ytick.alignment"]
+        return (self.get_yaxis_transform(which='tick2') +
+                mtransforms.ScaledTranslation(pad_points / 72, 0,
+                                              self.figure.dpi_scale_trans),
+                labels_align, "left")
+
+    def _update_transScale(self):
+        self.transScale.set(
+            mtransforms.blended_transform_factory(
+                self.xaxis.get_transform(), self.yaxis.get_transform()))
+        for line in getattr(self, "lines", []):  # Not set during init.
+            try:
+                line._transformed_path.invalidate()
+            except AttributeError:
+                pass
+
+    def get_position(self, original=False):
+        """
+        Get a copy of the axes rectangle as a `.Bbox`.
+
+        Parameters
+        ----------
+        original : bool
+            If ``True``, return the original position. Otherwise return the
+            active position. For an explanation of the positions see
+            `.set_position`.
+
+        Returns
+        -------
+        pos : `.Bbox`
+
+        """
+        if original:
+            return self._originalPosition.frozen()
+        else:
+            locator = self.get_axes_locator()
+            if not locator:
+                self.apply_aspect()
+            return self._position.frozen()
+
+    def set_position(self, pos, which='both'):
+        """
+        Set the axes position.
+
+        Axes have two position attributes. The 'original' position is the
+        position allocated for the Axes. The 'active' position is the
+        position the Axes is actually drawn at. These positions are usually
+        the same unless a fixed aspect is set to the Axes. See `.set_aspect`
+        for details.
+
+        Parameters
+        ----------
+        pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox`
+            The new position of the in `.Figure` coordinates.
+
+        which : {'both', 'active', 'original'}, optional
+            Determines which position variables to change.
+
+        """
+        self._set_position(pos, which=which)
+        # because this is being called externally to the library we
+        # zero the constrained layout parts.
+        self._layoutbox = None
+        self._poslayoutbox = None
+
+    def _set_position(self, pos, which='both'):
+        """
+        private version of set_position.  Call this internally
+        to get the same functionality of `get_position`, but not
+        to take the axis out of the constrained_layout
+        hierarchy.
+        """
+        if not isinstance(pos, mtransforms.BboxBase):
+            pos = mtransforms.Bbox.from_bounds(*pos)
+        for ax in self._twinned_axes.get_siblings(self):
+            if which in ('both', 'active'):
+                ax._position.set(pos)
+            if which in ('both', 'original'):
+                ax._originalPosition.set(pos)
+        self.stale = True
+
+    def reset_position(self):
+        """
+        Reset the active position to the original position.
+
+        This resets the a possible position change due to aspect constraints.
+        For an explanation of the positions see `.set_position`.
+        """
+        for ax in self._twinned_axes.get_siblings(self):
+            pos = ax.get_position(original=True)
+            ax.set_position(pos, which='active')
+
+    def set_axes_locator(self, locator):
+        """
+        Set the axes locator.
+
+        Parameters
+        ----------
+        locator : Callable[[Axes, Renderer], Bbox]
+        """
+        self._axes_locator = locator
+        self.stale = True
+
+    def get_axes_locator(self):
+        """
+        Return the axes_locator.
+        """
+        return self._axes_locator
+
+    def _set_artist_props(self, a):
+        """set the boilerplate props for artists added to axes"""
+        a.set_figure(self.figure)
+        if not a.is_transform_set():
+            a.set_transform(self.transData)
+
+        a.axes = self
+        if a.mouseover:
+            self._mouseover_set.add(a)
+
+    def _gen_axes_patch(self):
+        """
+        Returns
+        -------
+        Patch
+            The patch used to draw the background of the axes.  It is also used
+            as the clipping path for any data elements on the axes.
+
+            In the standard axes, this is a rectangle, but in other projections
+            it may not be.
+
+        Notes
+        -----
+        Intended to be overridden by new projection types.
+        """
+        return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0)
+
+    def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
+        """
+        Returns
+        -------
+        dict
+            Mapping of spine names to `Line2D` or `Patch` instances that are
+            used to draw axes spines.
+
+            In the standard axes, spines are single line segments, but in other
+            projections they may not be.
+
+        Notes
+        -----
+        Intended to be overridden by new projection types.
+        """
+        return OrderedDict((side, mspines.Spine.linear_spine(self, side))
+                           for side in ['left', 'right', 'bottom', 'top'])
+
+    def cla(self):
+        """Clear the current axes."""
+        # Note: this is called by Axes.__init__()
+
+        # stash the current visibility state
+        if hasattr(self, 'patch'):
+            patch_visible = self.patch.get_visible()
+        else:
+            patch_visible = True
+
+        xaxis_visible = self.xaxis.get_visible()
+        yaxis_visible = self.yaxis.get_visible()
+
+        self.xaxis.cla()
+        self.yaxis.cla()
+
+        for name, spine in self.spines.items():
+            spine.cla()
+
+        self.ignore_existing_data_limits = True
+        self.callbacks = cbook.CallbackRegistry()
+
+        if self._sharex is not None:
+            # major and minor are axis.Ticker class instances with
+            # locator and formatter attributes
+            self.xaxis.major = self._sharex.xaxis.major
+            self.xaxis.minor = self._sharex.xaxis.minor
+            x0, x1 = self._sharex.get_xlim()
+            self.set_xlim(x0, x1, emit=False,
+                          auto=self._sharex.get_autoscalex_on())
+            self.xaxis._scale = self._sharex.xaxis._scale
+        else:
+            self.xaxis._set_scale('linear')
+            try:
+                self.set_xlim(0, 1)
+            except TypeError:
+                pass
+
+        if self._sharey is not None:
+            self.yaxis.major = self._sharey.yaxis.major
+            self.yaxis.minor = self._sharey.yaxis.minor
+            y0, y1 = self._sharey.get_ylim()
+            self.set_ylim(y0, y1, emit=False,
+                          auto=self._sharey.get_autoscaley_on())
+            self.yaxis._scale = self._sharey.yaxis._scale
+        else:
+            self.yaxis._set_scale('linear')
+            try:
+                self.set_ylim(0, 1)
+            except TypeError:
+                pass
+        # update the minor locator for x and y axis based on rcParams
+        if rcParams['xtick.minor.visible']:
+            self.xaxis.set_minor_locator(mticker.AutoMinorLocator())
+
+        if rcParams['ytick.minor.visible']:
+            self.yaxis.set_minor_locator(mticker.AutoMinorLocator())
+
+        if self._sharex is None:
+            self._autoscaleXon = True
+        if self._sharey is None:
+            self._autoscaleYon = True
+        self._xmargin = rcParams['axes.xmargin']
+        self._ymargin = rcParams['axes.ymargin']
+        self._tight = None
+        self._use_sticky_edges = True
+        self._update_transScale()  # needed?
+
+        self._get_lines = _process_plot_var_args(self)
+        self._get_patches_for_fill = _process_plot_var_args(self, 'fill')
+
+        self._gridOn = rcParams['axes.grid']
+        self.lines = []
+        self.patches = []
+        self.texts = []
+        self.tables = []
+        self.artists = []
+        self.images = []
+        self._mouseover_set = _OrderedSet()
+        self.child_axes = []
+        self._current_image = None  # strictly for pyplot via _sci, _gci
+        self.legend_ = None
+        self.collections = []  # collection.Collection instances
+        self.containers = []
+
+        self.grid(False)  # Disable grid on init to use rcParameter
+        self.grid(self._gridOn, which=rcParams['axes.grid.which'],
+                  axis=rcParams['axes.grid.axis'])
+        props = font_manager.FontProperties(
+            size=rcParams['axes.titlesize'],
+            weight=rcParams['axes.titleweight'])
+
+        self.title = mtext.Text(
+            x=0.5, y=1.0, text='',
+            fontproperties=props,
+            verticalalignment='baseline',
+            horizontalalignment='center',
+            )
+        self._left_title = mtext.Text(
+            x=0.0, y=1.0, text='',
+            fontproperties=props.copy(),
+            verticalalignment='baseline',
+            horizontalalignment='left', )
+        self._right_title = mtext.Text(
+            x=1.0, y=1.0, text='',
+            fontproperties=props.copy(),
+            verticalalignment='baseline',
+            horizontalalignment='right',
+            )
+        title_offset_points = rcParams['axes.titlepad']
+        # refactor this out so it can be called in ax.set_title if
+        # pad argument used...
+        self._set_title_offset_trans(title_offset_points)
+        # determine if the title position has been set manually:
+        self._autotitlepos = None
+
+        for _title in (self.title, self._left_title, self._right_title):
+            self._set_artist_props(_title)
+
+        # The patch draws the background of the axes.  We want this to be below
+        # the other artists.  We use the frame to draw the edges so we are
+        # setting the edgecolor to None.
+        self.patch = self._gen_axes_patch()
+        self.patch.set_figure(self.figure)
+        self.patch.set_facecolor(self._facecolor)
+        self.patch.set_edgecolor('None')
+        self.patch.set_linewidth(0)
+        self.patch.set_transform(self.transAxes)
+
+        self.set_axis_on()
+
+        self.xaxis.set_clip_path(self.patch)
+        self.yaxis.set_clip_path(self.patch)
+
+        self._shared_x_axes.clean()
+        self._shared_y_axes.clean()
+        if self._sharex:
+            self.xaxis.set_visible(xaxis_visible)
+            self.patch.set_visible(patch_visible)
+
+        if self._sharey:
+            self.yaxis.set_visible(yaxis_visible)
+            self.patch.set_visible(patch_visible)
+
+        self.stale = True
+
+    def clear(self):
+        """Clear the axes."""
+        self.cla()
+
+    def get_facecolor(self):
+        """Get the facecolor of the Axes."""
+        return self.patch.get_facecolor()
+    get_fc = get_facecolor
+
+    def set_facecolor(self, color):
+        """
+        Set the facecolor of the Axes.
+
+        Parameters
+        ----------
+        color : color
+        """
+        self._facecolor = color
+        self.stale = True
+        return self.patch.set_facecolor(color)
+    set_fc = set_facecolor
+
+    def _set_title_offset_trans(self, title_offset_points):
+        """
+        Set the offset for the title either from rcParams['axes.titlepad']
+        or from set_title kwarg ``pad``.
+        """
+        self.titleOffsetTrans = mtransforms.ScaledTranslation(
+                0.0, title_offset_points / 72,
+                self.figure.dpi_scale_trans)
+        for _title in (self.title, self._left_title, self._right_title):
+            _title.set_transform(self.transAxes + self.titleOffsetTrans)
+            _title.set_clip_box(None)
+
+    def set_prop_cycle(self, *args, **kwargs):
+        """
+        Set the property cycle of the Axes.
+
+        The property cycle controls the style properties such as color,
+        marker and linestyle of future plot commands. The style properties
+        of data already added to the Axes are not modified.
+
+        Call signatures::
+
+          set_prop_cycle(cycler)
+          set_prop_cycle(label=values[, label2=values2[, ...]])
+          set_prop_cycle(label, values)
+
+        Form 1 sets given `~cycler.Cycler` object.
+
+        Form 2 creates a `~cycler.Cycler` which cycles over one or more
+        properties simultaneously and set it as the property cycle of the
+        axes. If multiple properties are given, their value lists must have
+        the same length. This is just a shortcut for explicitly creating a
+        cycler and passing it to the function, i.e. it's short for
+        ``set_prop_cycle(cycler(label=values label2=values2, ...))``.
+
+        Form 3 creates a `~cycler.Cycler` for a single property and set it
+        as the property cycle of the axes. This form exists for compatibility
+        with the original `cycler.cycler` interface. Its use is discouraged
+        in favor of the kwarg form, i.e. ``set_prop_cycle(label=values)``.
+
+        Parameters
+        ----------
+        cycler : Cycler
+            Set the given Cycler. *None* resets to the cycle defined by the
+            current style.
+
+        label : str
+            The property key. Must be a valid `.Artist` property.
+            For example, 'color' or 'linestyle'. Aliases are allowed,
+            such as 'c' for 'color' and 'lw' for 'linewidth'.
+
+        values : iterable
+            Finite-length iterable of the property values. These values
+            are validated and will raise a ValueError if invalid.
+
+        Examples
+        --------
+        Setting the property cycle for a single property:
+
+        >>> ax.set_prop_cycle(color=['red', 'green', 'blue'])
+
+        Setting the property cycle for simultaneously cycling over multiple
+        properties (e.g. red circle, green plus, blue cross):
+
+        >>> ax.set_prop_cycle(color=['red', 'green', 'blue'],
+        ...                   marker=['o', '+', 'x'])
+
+        See Also
+        --------
+        matplotlib.rcsetup.cycler
+            Convenience function for creating validated cyclers for properties.
+        cycler.cycler
+            The original function for creating unvalidated cyclers.
+
+        """
+        if args and kwargs:
+            raise TypeError("Cannot supply both positional and keyword "
+                            "arguments to this method.")
+        # Can't do `args == (None,)` as that crashes cycler.
+        if len(args) == 1 and args[0] is None:
+            prop_cycle = None
+        else:
+            prop_cycle = cycler(*args, **kwargs)
+        self._get_lines.set_prop_cycle(prop_cycle)
+        self._get_patches_for_fill.set_prop_cycle(prop_cycle)
+
+    def get_aspect(self):
+        return self._aspect
+
+    def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
+        """
+        Set the aspect of the axis scaling, i.e. the ratio of y-unit to x-unit.
+
+        Parameters
+        ----------
+        aspect : {'auto', 'equal'} or num
+            Possible values:
+
+            ========   ================================================
+            value      description
+            ========   ================================================
+            'auto'     automatic; fill the position rectangle with data
+            'equal'    same scaling from data to plot units for x and y
+             num       a circle will be stretched such that the height
+                       is num times the width. aspect=1 is the same as
+                       aspect='equal'.
+            ========   ================================================
+
+        adjustable : None or {'box', 'datalim'}, optional
+            If not ``None``, this defines which parameter will be adjusted to
+            meet the required aspect. See `.set_adjustable` for further
+            details.
+
+        anchor : None or str or 2-tuple of float, optional
+            If not ``None``, this defines where the Axes will be drawn if there
+            is extra space due to aspect constraints. The most common way to
+            to specify the anchor are abbreviations of cardinal directions:
+
+            =====   =====================
+            value   description
+            =====   =====================
+            'C'     centered
+            'SW'    lower left corner
+            'S'     middle of bottom edge
+            'SE'    lower right corner
+            etc.
+            =====   =====================
+
+            See `.set_anchor` for further details.
+
+        share : bool, optional
+            If ``True``, apply the settings to all shared Axes.
+            Default is ``False``.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.set_adjustable
+            defining the parameter to adjust in order to meet the required
+            aspect.
+        matplotlib.axes.Axes.set_anchor
+            defining the position in case of extra space.
+        """
+        if not (cbook._str_equal(aspect, 'equal')
+                or cbook._str_equal(aspect, 'auto')):
+            aspect = float(aspect)  # raise ValueError if necessary
+
+        if (not cbook._str_equal(aspect, 'auto')) and self.name == '3d':
+            raise NotImplementedError(
+                'It is not currently possible to manually set the aspect '
+                'on 3D axes')
+
+        if share:
+            axes = {*self._shared_x_axes.get_siblings(self),
+                    *self._shared_y_axes.get_siblings(self)}
+        else:
+            axes = [self]
+
+        for ax in axes:
+            ax._aspect = aspect
+
+        if adjustable is None:
+            adjustable = self._adjustable
+        self.set_adjustable(adjustable, share=share)  # Handle sharing.
+
+        if anchor is not None:
+            self.set_anchor(anchor, share=share)
+        self.stale = True
+
+    def get_adjustable(self):
+        return self._adjustable
+
+    def set_adjustable(self, adjustable, share=False):
+        """
+        Define which parameter the Axes will change to achieve a given aspect.
+
+        Parameters
+        ----------
+        adjustable : {'box', 'datalim'}
+            If 'box', change the physical dimensions of the Axes.
+            If 'datalim', change the ``x`` or ``y`` data limits.
+
+        share : bool, optional
+            If ``True``, apply the settings to all shared Axes.
+            Default is ``False``.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.set_aspect
+            for a description of aspect handling.
+
+        Notes
+        -----
+        Shared Axes (of which twinned Axes are a special case)
+        impose restrictions on how aspect ratios can be imposed.
+        For twinned Axes, use 'datalim'.  For Axes that share both
+        x and y, use 'box'.  Otherwise, either 'datalim' or 'box'
+        may be used.  These limitations are partly a requirement
+        to avoid over-specification, and partly a result of the
+        particular implementation we are currently using, in
+        which the adjustments for aspect ratios are done sequentially
+        and independently on each Axes as it is drawn.
+        """
+        cbook._check_in_list(["box", "datalim"], adjustable=adjustable)
+        if share:
+            axs = {*self._shared_x_axes.get_siblings(self),
+                   *self._shared_y_axes.get_siblings(self)}
+        else:
+            axs = [self]
+        if (adjustable == "datalim"
+                and any(getattr(ax.get_data_ratio, "__func__", None)
+                        != _AxesBase.get_data_ratio
+                        for ax in axs)):
+            # Limits adjustment by apply_aspect assumes that the axes' aspect
+            # ratio can be computed from the data limits and scales.
+            raise ValueError("Cannot set axes adjustable to 'datalim' for "
+                             "Axes which override 'get_data_ratio'")
+        for ax in axs:
+            ax._adjustable = adjustable
+        self.stale = True
+
+    def get_anchor(self):
+        """
+        Get the anchor location.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.set_anchor
+            for a description of the anchor.
+        matplotlib.axes.Axes.set_aspect
+            for a description of aspect handling.
+        """
+        return self._anchor
+
+    def set_anchor(self, anchor, share=False):
+        """
+        Define the anchor location.
+
+        The actual drawing area (active position) of the Axes may be smaller
+        than the Bbox (original position) when a fixed aspect is required. The
+        anchor defines where the drawing area will be located within the
+        available space.
+
+        Parameters
+        ----------
+        anchor : 2-tuple of floats or {'C', 'SW', 'S', 'SE', ...}
+            The anchor position may be either:
+
+            - a sequence (*cx*, *cy*). *cx* and *cy* may range from 0
+              to 1, where 0 is left or bottom and 1 is right or top.
+
+            - a string using cardinal directions as abbreviation:
+
+              - 'C' for centered
+              - 'S' (south) for bottom-center
+              - 'SW' (south west) for bottom-left
+              - etc.
+
+              Here is an overview of the possible positions:
+
+              +------+------+------+
+              | 'NW' | 'N'  | 'NE' |
+              +------+------+------+
+              | 'W'  | 'C'  | 'E'  |
+              +------+------+------+
+              | 'SW' | 'S'  | 'SE' |
+              +------+------+------+
+
+        share : bool, optional
+            If ``True``, apply the settings to all shared Axes.
+            Default is ``False``.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.set_aspect
+            for a description of aspect handling.
+        """
+        if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2):
+            raise ValueError('argument must be among %s' %
+                             ', '.join(mtransforms.Bbox.coefs))
+        if share:
+            axes = {*self._shared_x_axes.get_siblings(self),
+                    *self._shared_y_axes.get_siblings(self)}
+        else:
+            axes = [self]
+        for ax in axes:
+            ax._anchor = anchor
+
+        self.stale = True
+
+    def get_data_ratio(self):
+        """
+        Return the aspect ratio of the scaled data.
+
+        Notes
+        -----
+        This method is intended to be overridden by new projection types.
+        """
+        txmin, txmax = self.xaxis.get_transform().transform(self.get_xbound())
+        tymin, tymax = self.yaxis.get_transform().transform(self.get_ybound())
+        xsize = max(abs(txmax - txmin), 1e-30)
+        ysize = max(abs(tymax - tymin), 1e-30)
+        return ysize / xsize
+
+    @cbook.deprecated("3.2")
+    def get_data_ratio_log(self):
+        """
+        Return the aspect ratio of the raw data in log scale.
+
+        Notes
+        -----
+        Will be used when both axis are in log scale.
+        """
+        xmin, xmax = self.get_xbound()
+        ymin, ymax = self.get_ybound()
+
+        xsize = max(abs(math.log10(xmax) - math.log10(xmin)), 1e-30)
+        ysize = max(abs(math.log10(ymax) - math.log10(ymin)), 1e-30)
+
+        return ysize / xsize
+
+    def apply_aspect(self, position=None):
+        """
+        Adjust the Axes for a specified data aspect ratio.
+
+        Depending on `.get_adjustable` this will modify either the Axes box
+        (position) or the view limits. In the former case, `.get_anchor`
+        will affect the position.
+
+        Notes
+        -----
+        This is called automatically when each Axes is drawn.  You may need
+        to call it yourself if you need to update the Axes position and/or
+        view limits before the Figure is drawn.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.set_aspect
+            for a description of aspect ratio handling.
+        matplotlib.axes.Axes.set_adjustable
+            defining the parameter to adjust in order to meet the required
+            aspect.
+        matplotlib.axes.Axes.set_anchor
+            defining the position in case of extra space.
+        """
+        if position is None:
+            position = self.get_position(original=True)
+
+        aspect = self.get_aspect()
+
+        if aspect == 'auto':
+            self._set_position(position, which='active')
+            return
+
+        if aspect == 'equal':
+            aspect = 1
+
+        fig_width, fig_height = self.get_figure().get_size_inches()
+        fig_aspect = fig_height / fig_width
+
+        if self._adjustable == 'box':
+            if self in self._twinned_axes:
+                raise RuntimeError("Adjustable 'box' is not allowed in a "
+                                   "twinned Axes; use 'datalim' instead")
+            box_aspect = aspect * self.get_data_ratio()
+            pb = position.frozen()
+            pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
+            self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
+            return
+
+        # self._adjustable == 'datalim'
+
+        # reset active to original in case it had been changed by prior use
+        # of 'box'
+        self._set_position(position, which='active')
+
+        x_trf = self.xaxis.get_transform()
+        y_trf = self.yaxis.get_transform()
+        xmin, xmax = x_trf.transform(self.get_xbound())
+        ymin, ymax = y_trf.transform(self.get_ybound())
+        xsize = max(abs(xmax - xmin), 1e-30)
+        ysize = max(abs(ymax - ymin), 1e-30)
+
+        l, b, w, h = position.bounds
+        box_aspect = fig_aspect * (h / w)
+        data_ratio = box_aspect / aspect
+
+        y_expander = data_ratio * xsize / ysize - 1
+        # If y_expander > 0, the dy/dx viewLim ratio needs to increase
+        if abs(y_expander) < 0.005:
+            return
+
+        dL = self.dataLim
+        x0, x1 = x_trf.transform(dL.intervalx)
+        y0, y1 = y_trf.transform(dL.intervaly)
+        xr = 1.05 * (x1 - x0)
+        yr = 1.05 * (y1 - y0)
+
+        xmarg = xsize - xr
+        ymarg = ysize - yr
+        Ysize = data_ratio * xsize
+        Xsize = ysize / data_ratio
+        Xmarg = Xsize - xr
+        Ymarg = Ysize - yr
+        # Setting these targets to, e.g., 0.05*xr does not seem to help.
+        xm = 0
+        ym = 0
+
+        shared_x = self in self._shared_x_axes
+        shared_y = self in self._shared_y_axes
+        # Not sure whether we need this check:
+        if shared_x and shared_y:
+            raise RuntimeError("adjustable='datalim' is not allowed when both "
+                               "axes are shared")
+
+        # If y is shared, then we are only allowed to change x, etc.
+        if shared_y:
+            adjust_y = False
+        else:
+            if xmarg > xm and ymarg > ym:
+                adjy = ((Ymarg > 0 and y_expander < 0) or
+                        (Xmarg < 0 and y_expander > 0))
+            else:
+                adjy = y_expander > 0
+            adjust_y = shared_x or adjy  # (Ymarg > xmarg)
+
+        if adjust_y:
+            yc = 0.5 * (ymin + ymax)
+            y0 = yc - Ysize / 2.0
+            y1 = yc + Ysize / 2.0
+            self.set_ybound(y_trf.inverted().transform([y0, y1]))
+        else:
+            xc = 0.5 * (xmin + xmax)
+            x0 = xc - Xsize / 2.0
+            x1 = xc + Xsize / 2.0
+            self.set_xbound(x_trf.inverted().transform([x0, x1]))
+
+    def axis(self, *args, emit=True, **kwargs):
+        """
+        Convenience method to get or set some axis properties.
+
+        Call signatures::
+
+          xmin, xmax, ymin, ymax = axis()
+          xmin, xmax, ymin, ymax = axis([xmin, xmax, ymin, ymax])
+          xmin, xmax, ymin, ymax = axis(option)
+          xmin, xmax, ymin, ymax = axis(**kwargs)
+
+        Parameters
+        ----------
+        xmin, xmax, ymin, ymax : float, optional
+            The axis limits to be set.  This can also be achieved using ::
+
+                ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
+
+        option : bool or str
+            If a bool, turns axis lines and labels on or off. If a string,
+            possible values are:
+
+            ======== ==========================================================
+            Value    Description
+            ======== ==========================================================
+            'on'     Turn on axis lines and labels. Same as ``True``.
+            'off'    Turn off axis lines and labels. Same as ``False``.
+            'equal'  Set equal scaling (i.e., make circles circular) by
+                     changing axis limits. This is the same as
+                     ``ax.set_aspect('equal', adjustable='datalim')``.
+                     Explicit data limits may not be respected in this case.
+            'scaled' Set equal scaling (i.e., make circles circular) by
+                     changing dimensions of the plot box. This is the same as
+                     ``ax.set_aspect('equal', adjustable='box', anchor='C')``.
+                     Additionally, further autoscaling will be disabled.
+            'tight'  Set limits just large enough to show all data, then
+                     disable further autoscaling.
+            'auto'   Automatic scaling (fill plot box with data).
+            'normal' Same as 'auto'; deprecated.
+            'image'  'scaled' with axis limits equal to data limits.
+            'square' Square plot; similar to 'scaled', but initially forcing
+                     ``xmax-xmin == ymax-ymin``.
+            ======== ==========================================================
+
+        emit : bool, optional, default *True*
+            Whether observers are notified of the axis limit change.
+            This option is passed on to `~.Axes.set_xlim` and
+            `~.Axes.set_ylim`.
+
+        Returns
+        -------
+        xmin, xmax, ymin, ymax : float
+            The axis limits.
+
+        See also
+        --------
+        matplotlib.axes.Axes.set_xlim
+        matplotlib.axes.Axes.set_ylim
+        """
+        if len(args) == 1 and isinstance(args[0], (str, bool)):
+            s = args[0]
+            if s is True:
+                s = 'on'
+            if s is False:
+                s = 'off'
+            s = s.lower()
+            if s == 'on':
+                self.set_axis_on()
+            elif s == 'off':
+                self.set_axis_off()
+            elif s in ('equal', 'tight', 'scaled', 'normal',
+                       'auto', 'image', 'square'):
+                if s == 'normal':
+                    cbook.warn_deprecated(
+                        "3.1", message="Passing 'normal' to axis() is "
+                        "deprecated since %(since)s; use 'auto' instead.")
+                self.set_autoscale_on(True)
+                self.set_aspect('auto')
+                self.autoscale_view(tight=False)
+                # self.apply_aspect()
+                if s == 'equal':
+                    self.set_aspect('equal', adjustable='datalim')
+                elif s == 'scaled':
+                    self.set_aspect('equal', adjustable='box', anchor='C')
+                    self.set_autoscale_on(False)  # Req. by Mark Bakker
+                elif s == 'tight':
+                    self.autoscale_view(tight=True)
+                    self.set_autoscale_on(False)
+                elif s == 'image':
+                    self.autoscale_view(tight=True)
+                    self.set_autoscale_on(False)
+                    self.set_aspect('equal', adjustable='box', anchor='C')
+                elif s == 'square':
+                    self.set_aspect('equal', adjustable='box', anchor='C')
+                    self.set_autoscale_on(False)
+                    xlim = self.get_xlim()
+                    ylim = self.get_ylim()
+                    edge_size = max(np.diff(xlim), np.diff(ylim))[0]
+                    self.set_xlim([xlim[0], xlim[0] + edge_size],
+                                  emit=emit, auto=False)
+                    self.set_ylim([ylim[0], ylim[0] + edge_size],
+                                  emit=emit, auto=False)
+            else:
+                raise ValueError('Unrecognized string %s to axis; '
+                                 'try on or off' % s)
+        else:
+            if len(args) >= 1:
+                if len(args) != 1:
+                    cbook.warn_deprecated(
+                        "3.2", message="Passing more than one positional "
+                        "argument to axis() is deprecated and will raise a "
+                        "TypeError %(removal)s.")
+                limits = args[0]
+                try:
+                    xmin, xmax, ymin, ymax = limits
+                except (TypeError, ValueError):
+                    raise TypeError('the first argument to axis() must be an '
+                                    'interable of the form '
+                                    '[xmin, xmax, ymin, ymax]')
+            else:
+                xmin = kwargs.pop('xmin', None)
+                xmax = kwargs.pop('xmax', None)
+                ymin = kwargs.pop('ymin', None)
+                ymax = kwargs.pop('ymax', None)
+            xauto = (None  # Keep autoscale state as is.
+                     if xmin is None and xmax is None
+                     else False)  # Turn off autoscale.
+            yauto = (None
+                     if ymin is None and ymax is None
+                     else False)
+            self.set_xlim(xmin, xmax, emit=emit, auto=xauto)
+            self.set_ylim(ymin, ymax, emit=emit, auto=yauto)
+        if kwargs:
+            cbook.warn_deprecated(
+                "3.1", message="Passing unsupported keyword arguments to "
+                "axis() will raise a TypeError %(removal)s.")
+        return (*self.get_xlim(), *self.get_ylim())
+
+    def get_legend(self):
+        """Return the `Legend` instance, or None if no legend is defined."""
+        return self.legend_
+
+    def get_images(self):
+        """return a list of Axes images contained by the Axes"""
+        return cbook.silent_list('AxesImage', self.images)
+
+    def get_lines(self):
+        """Return a list of lines contained by the Axes"""
+        return cbook.silent_list('Line2D', self.lines)
+
+    def get_xaxis(self):
+        """Return the XAxis instance."""
+        return self.xaxis
+
+    def get_xgridlines(self):
+        """Get the x grid lines as a list of `Line2D` instances."""
+        return self.xaxis.get_gridlines()
+
+    def get_xticklines(self):
+        """Get the x tick lines as a list of `Line2D` instances."""
+        return self.xaxis.get_ticklines()
+
+    def get_yaxis(self):
+        """Return the YAxis instance."""
+        return self.yaxis
+
+    def get_ygridlines(self):
+        """Get the y grid lines as a list of `Line2D` instances."""
+        return self.yaxis.get_gridlines()
+
+    def get_yticklines(self):
+        """Get the y tick lines as a list of `Line2D` instances."""
+        return self.yaxis.get_ticklines()
+
+    # Adding and tracking artists
+
+    def _sci(self, im):
+        """Set the current image.
+
+        This image will be the target of colormap functions like
+        `~.pyplot.viridis`, and other functions such as `~.pyplot.clim`.  The
+        current image is an attribute of the current axes.
+        """
+        if isinstance(im, mpl.contour.ContourSet):
+            if im.collections[0] not in self.collections:
+                raise ValueError("ContourSet must be in current Axes")
+        elif im not in self.images and im not in self.collections:
+            raise ValueError("Argument must be an image, collection, or "
+                             "ContourSet in this Axes")
+        self._current_image = im
+
+    def _gci(self):
+        """
+        Helper for :func:`~matplotlib.pyplot.gci`;
+        do not use elsewhere.
+        """
+        return self._current_image
+
+    def has_data(self):
+        """
+        Return *True* if any artists have been added to axes.
+
+        This should not be used to determine whether the *dataLim*
+        need to be updated, and may not actually be useful for
+        anything.
+        """
+        return (
+            len(self.collections) +
+            len(self.images) +
+            len(self.lines) +
+            len(self.patches)) > 0
+
+    def add_artist(self, a):
+        """
+        Add an `~.Artist` to the axes, and return the artist.
+
+        Use `add_artist` only for artists for which there is no dedicated
+        "add" method; and if necessary, use a method such as `update_datalim`
+        to manually update the dataLim if the artist is to be included in
+        autoscaling.
+
+        If no ``transform`` has been specified when creating the artist (e.g.
+        ``artist.get_transform() == None``) then the transform is set to
+        ``ax.transData``.
+        """
+        a.axes = self
+        self.artists.append(a)
+        a._remove_method = self.artists.remove
+        self._set_artist_props(a)
+        a.set_clip_path(self.patch)
+        self.stale = True
+        return a
+
+    def add_child_axes(self, ax):
+        """
+        Add an `~.AxesBase` to the axes' children; return the child axes.
+
+        This is the lowlevel version.  See `.axes.Axes.inset_axes`.
+        """
+
+        # normally axes have themselves as the axes, but these need to have
+        # their parent...
+        # Need to bypass the getter...
+        ax._axes = self
+        ax.stale_callback = martist._stale_axes_callback
+
+        self.child_axes.append(ax)
+        ax._remove_method = self.child_axes.remove
+        self.stale = True
+        return ax
+
+    def add_collection(self, collection, autolim=True):
+        """
+        Add a `~.Collection` to the axes' collections; return the collection.
+        """
+        label = collection.get_label()
+        if not label:
+            collection.set_label('_collection%d' % len(self.collections))
+        self.collections.append(collection)
+        collection._remove_method = self.collections.remove
+        self._set_artist_props(collection)
+
+        if collection.get_clip_path() is None:
+            collection.set_clip_path(self.patch)
+
+        if autolim:
+            # Make sure viewLim is not stale (mostly to match
+            # pre-lazy-autoscale behavior, which is not really better).
+            self._unstale_viewLim()
+            self.update_datalim(collection.get_datalim(self.transData))
+
+        self.stale = True
+        return collection
+
+    def add_image(self, image):
+        """
+        Add an `~.AxesImage` to the axes' images; return the image.
+        """
+        self._set_artist_props(image)
+        if not image.get_label():
+            image.set_label('_image%d' % len(self.images))
+        self.images.append(image)
+        image._remove_method = self.images.remove
+        self.stale = True
+        return image
+
+    def _update_image_limits(self, image):
+        xmin, xmax, ymin, ymax = image.get_extent()
+        self.axes.update_datalim(((xmin, ymin), (xmax, ymax)))
+
+    def add_line(self, line):
+        """
+        Add a `.Line2D` to the axes' lines; return the line.
+        """
+        self._set_artist_props(line)
+        if line.get_clip_path() is None:
+            line.set_clip_path(self.patch)
+
+        self._update_line_limits(line)
+        if not line.get_label():
+            line.set_label('_line%d' % len(self.lines))
+        self.lines.append(line)
+        line._remove_method = self.lines.remove
+        self.stale = True
+        return line
+
+    def _add_text(self, txt):
+        """
+        Add a `~.Text` to the axes' texts; return the text.
+        """
+        self._set_artist_props(txt)
+        self.texts.append(txt)
+        txt._remove_method = self.texts.remove
+        self.stale = True
+        return txt
+
+    def _update_line_limits(self, line):
+        """
+        Figures out the data limit of the given line, updating self.dataLim.
+        """
+        path = line.get_path()
+        if path.vertices.size == 0:
+            return
+
+        line_trans = line.get_transform()
+
+        if line_trans == self.transData:
+            data_path = path
+
+        elif any(line_trans.contains_branch_seperately(self.transData)):
+            # identify the transform to go from line's coordinates
+            # to data coordinates
+            trans_to_data = line_trans - self.transData
+
+            # if transData is affine we can use the cached non-affine component
+            # of line's path. (since the non-affine part of line_trans is
+            # entirely encapsulated in trans_to_data).
+            if self.transData.is_affine:
+                line_trans_path = line._get_transformed_path()
+                na_path, _ = line_trans_path.get_transformed_path_and_affine()
+                data_path = trans_to_data.transform_path_affine(na_path)
+            else:
+                data_path = trans_to_data.transform_path(path)
+        else:
+            # for backwards compatibility we update the dataLim with the
+            # coordinate range of the given path, even though the coordinate
+            # systems are completely different. This may occur in situations
+            # such as when ax.transAxes is passed through for absolute
+            # positioning.
+            data_path = path
+
+        if data_path.vertices.size > 0:
+            updatex, updatey = line_trans.contains_branch_seperately(
+                self.transData)
+            self.dataLim.update_from_path(data_path,
+                                          self.ignore_existing_data_limits,
+                                          updatex=updatex,
+                                          updatey=updatey)
+            self.ignore_existing_data_limits = False
+
+    def add_patch(self, p):
+        """
+        Add a `~.Patch` to the axes' patches; return the patch.
+        """
+        self._set_artist_props(p)
+        if p.get_clip_path() is None:
+            p.set_clip_path(self.patch)
+        self._update_patch_limits(p)
+        self.patches.append(p)
+        p._remove_method = self.patches.remove
+        return p
+
+    def _update_patch_limits(self, patch):
+        """update the data limits for patch *p*"""
+        # hist can add zero height Rectangles, which is useful to keep
+        # the bins, counts and patches lined up, but it throws off log
+        # scaling.  We'll ignore rects with zero height or width in
+        # the auto-scaling
+
+        # cannot check for '==0' since unitized data may not compare to zero
+        # issue #2150 - we update the limits if patch has non zero width
+        # or height.
+        if (isinstance(patch, mpatches.Rectangle) and
+                ((not patch.get_width()) and (not patch.get_height()))):
+            return
+        vertices = patch.get_path().vertices
+        if vertices.size > 0:
+            xys = patch.get_patch_transform().transform(vertices)
+            if patch.get_data_transform() != self.transData:
+                patch_to_data = (patch.get_data_transform() -
+                                 self.transData)
+                xys = patch_to_data.transform(xys)
+
+            updatex, updatey = patch.get_transform().\
+                contains_branch_seperately(self.transData)
+            self.update_datalim(xys, updatex=updatex,
+                                updatey=updatey)
+
+    def add_table(self, tab):
+        """
+        Add a `~.Table` to the axes' tables; return the table.
+        """
+        self._set_artist_props(tab)
+        self.tables.append(tab)
+        tab.set_clip_path(self.patch)
+        tab._remove_method = self.tables.remove
+        return tab
+
+    def add_container(self, container):
+        """
+        Add a `~.Container` to the axes' containers; return the container.
+        """
+        label = container.get_label()
+        if not label:
+            container.set_label('_container%d' % len(self.containers))
+        self.containers.append(container)
+        container._remove_method = self.containers.remove
+        return container
+
+    def _on_units_changed(self, scalex=False, scaley=False):
+        """
+        Callback for processing changes to axis units.
+
+        Currently requests updates of data limits and view limits.
+        """
+        self.relim()
+        self._request_autoscale_view(scalex=scalex, scaley=scaley)
+
+    def relim(self, visible_only=False):
+        """
+        Recompute the data limits based on current artists.
+
+        At present, `~.Collection` instances are not supported.
+
+        Parameters
+        ----------
+        visible_only : bool
+            Whether to exclude invisible artists.  Defaults to False.
+        """
+        # Collections are deliberately not supported (yet); see
+        # the TODO note in artists.py.
+        self.dataLim.ignore(True)
+        self.dataLim.set_points(mtransforms.Bbox.null().get_points())
+        self.ignore_existing_data_limits = True
+
+        for line in self.lines:
+            if not visible_only or line.get_visible():
+                self._update_line_limits(line)
+
+        for p in self.patches:
+            if not visible_only or p.get_visible():
+                self._update_patch_limits(p)
+
+        for image in self.images:
+            if not visible_only or image.get_visible():
+                self._update_image_limits(image)
+
+    def update_datalim(self, xys, updatex=True, updatey=True):
+        """
+        Extend the `~.Axes.dataLim` Bbox to include the given points.
+
+        If no data is set currently, the Bbox will ignore its limits and set
+        the bound to be the bounds of the xydata (*xys*). Otherwise, it will
+        compute the bounds of the union of its current data and the data in
+        *xys*.
+
+        Parameters
+        ----------
+        xys : 2D array-like
+            The points to include in the data limits Bbox. This can be either
+            a list of (x, y) tuples or a Nx2 array.
+
+        updatex, updatey : bool, optional, default *True*
+            Whether to update the x/y limits.
+        """
+        xys = np.asarray(xys)
+        if not len(xys):
+            return
+        self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits,
+                                         updatex=updatex, updatey=updatey)
+        self.ignore_existing_data_limits = False
+
+    def update_datalim_bounds(self, bounds):
+        """
+        Extend the `~.Axes.datalim` Bbox to include the given
+        `~matplotlib.transforms.Bbox`.
+
+        Parameters
+        ----------
+        bounds : `~matplotlib.transforms.Bbox`
+        """
+        self.dataLim.set(mtransforms.Bbox.union([self.dataLim, bounds]))
+
+    def _process_unit_info(self, xdata=None, ydata=None, kwargs=None):
+        """Look for unit *kwargs* and update the axis instances as necessary"""
+
+        def _process_single_axis(data, axis, unit_name, kwargs):
+            # Return if there's no axis set
+            if axis is None:
+                return kwargs
+
+            if data is not None:
+                # We only need to update if there is nothing set yet.
+                if not axis.have_units():
+                    axis.update_units(data)
+
+            # Check for units in the kwargs, and if present update axis
+            if kwargs is not None:
+                units = kwargs.pop(unit_name, axis.units)
+                if self.name == 'polar':
+                    polar_units = {'xunits': 'thetaunits', 'yunits': 'runits'}
+                    units = kwargs.pop(polar_units[unit_name], units)
+
+                if units != axis.units:
+                    axis.set_units(units)
+                    # If the units being set imply a different converter,
+                    # we need to update.
+                    if data is not None:
+                        axis.update_units(data)
+            return kwargs
+
+        kwargs = _process_single_axis(xdata, self.xaxis, 'xunits', kwargs)
+        kwargs = _process_single_axis(ydata, self.yaxis, 'yunits', kwargs)
+        return kwargs
+
+    def in_axes(self, mouseevent):
+        """
+        Return *True* if the given *mouseevent* (in display coords)
+        is in the Axes
+        """
+        return self.patch.contains(mouseevent)[0]
+
+    def get_autoscale_on(self):
+        """
+        Get whether autoscaling is applied for both axes on plot commands
+        """
+        return self._autoscaleXon and self._autoscaleYon
+
+    def get_autoscalex_on(self):
+        """
+        Get whether autoscaling for the x-axis is applied on plot commands
+        """
+        return self._autoscaleXon
+
+    def get_autoscaley_on(self):
+        """
+        Get whether autoscaling for the y-axis is applied on plot commands
+        """
+        return self._autoscaleYon
+
+    def set_autoscale_on(self, b):
+        """
+        Set whether autoscaling is applied on plot commands
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._autoscaleXon = b
+        self._autoscaleYon = b
+
+    def set_autoscalex_on(self, b):
+        """
+        Set whether autoscaling for the x-axis is applied on plot commands
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._autoscaleXon = b
+
+    def set_autoscaley_on(self, b):
+        """
+        Set whether autoscaling for the y-axis is applied on plot commands
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._autoscaleYon = b
+
+    @property
+    def use_sticky_edges(self):
+        """
+        When autoscaling, whether to obey all `Artist.sticky_edges`.
+
+        Default is ``True``.
+
+        Setting this to ``False`` ensures that the specified margins
+        will be applied, even if the plot includes an image, for
+        example, which would otherwise force a view limit to coincide
+        with its data limit.
+
+        The changing this property does not change the plot until
+        `autoscale` or `autoscale_view` is called.
+        """
+        return self._use_sticky_edges
+
+    @use_sticky_edges.setter
+    def use_sticky_edges(self, b):
+        self._use_sticky_edges = bool(b)
+        # No effect until next autoscaling, which will mark the axes as stale.
+
+    def set_xmargin(self, m):
+        """
+        Set padding of X data limits prior to autoscaling.
+
+        *m* times the data interval will be added to each
+        end of that interval before it is used in autoscaling.
+        For example, if your data is in the range [0, 2], a factor of
+        ``m = 0.1`` will result in a range [-0.2, 2.2].
+
+        Negative values -0.5 < m < 0 will result in clipping of the data range.
+        I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in
+        a range [0.2, 1.8].
+
+        Parameters
+        ----------
+        m : float greater than -0.5
+        """
+        if m <= -0.5:
+            raise ValueError("margin must be greater than -0.5")
+        self._xmargin = m
+        self.stale = True
+
+    def set_ymargin(self, m):
+        """
+        Set padding of Y data limits prior to autoscaling.
+
+        *m* times the data interval will be added to each
+        end of that interval before it is used in autoscaling.
+        For example, if your data is in the range [0, 2], a factor of
+        ``m = 0.1`` will result in a range [-0.2, 2.2].
+
+        Negative values -0.5 < m < 0 will result in clipping of the data range.
+        I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in
+        a range [0.2, 1.8].
+
+        Parameters
+        ----------
+        m : float greater than -0.5
+        """
+        if m <= -0.5:
+            raise ValueError("margin must be greater than -0.5")
+        self._ymargin = m
+        self.stale = True
+
+    def margins(self, *margins, x=None, y=None, tight=True):
+        """
+        Set or retrieve autoscaling margins.
+
+        The padding added to each limit of the axes is the *margin*
+        times the data interval. All input parameters must be floats
+        within the range [0, 1]. Passing both positional and keyword
+        arguments is invalid and will raise a TypeError. If no
+        arguments (positional or otherwise) are provided, the current
+        margins will remain in place and simply be returned.
+
+        Specifying any margin changes only the autoscaling; for example,
+        if *xmargin* is not None, then *xmargin* times the X data
+        interval will be added to each end of that interval before
+        it is used in autoscaling.
+
+        Parameters
+        ----------
+        *margins : float, optional
+            If a single positional argument is provided, it specifies
+            both margins of the x-axis and y-axis limits. If two
+            positional arguments are provided, they will be interpreted
+            as *xmargin*, *ymargin*. If setting the margin on a single
+            axis is desired, use the keyword arguments described below.
+
+        x, y : float, optional
+            Specific margin values for the x-axis and y-axis,
+            respectively. These cannot be used with positional
+            arguments, but can be used individually to alter on e.g.,
+            only the y-axis.
+
+        tight : bool or None, default is True
+            The *tight* parameter is passed to :meth:`autoscale_view`,
+            which is executed after a margin is changed; the default
+            here is *True*, on the assumption that when margins are
+            specified, no additional padding to match tick marks is
+            usually desired.  Set *tight* to *None* will preserve
+            the previous setting.
+
+        Returns
+        -------
+        xmargin, ymargin : float
+
+        Notes
+        -----
+        If a previously used Axes method such as :meth:`pcolor` has set
+        :attr:`use_sticky_edges` to `True`, only the limits not set by
+        the "sticky artists" will be modified. To force all of the
+        margins to be set, set :attr:`use_sticky_edges` to `False`
+        before calling :meth:`margins`.
+        """
+
+        if margins and x is not None and y is not None:
+            raise TypeError('Cannot pass both positional and keyword '
+                            'arguments for x and/or y.')
+        elif len(margins) == 1:
+            x = y = margins[0]
+        elif len(margins) == 2:
+            x, y = margins
+        elif margins:
+            raise TypeError('Must pass a single positional argument for all '
+                            'margins, or one for each margin (x, y).')
+
+        if x is None and y is None:
+            if tight is not True:
+                cbook._warn_external(f'ignoring tight={tight!r} in get mode')
+            return self._xmargin, self._ymargin
+
+        if x is not None:
+            self.set_xmargin(x)
+        if y is not None:
+            self.set_ymargin(y)
+
+        self._request_autoscale_view(
+            tight=tight, scalex=(x is not None), scaley=(y is not None)
+        )
+
+    def set_rasterization_zorder(self, z):
+        """
+        Parameters
+        ----------
+        z : float or None
+            zorder below which artists are rasterized.  ``None`` means that
+            artists do not get rasterized based on zorder.
+        """
+        self._rasterization_zorder = z
+        self.stale = True
+
+    def get_rasterization_zorder(self):
+        """Return the zorder value below which artists will be rasterized."""
+        return self._rasterization_zorder
+
+    def autoscale(self, enable=True, axis='both', tight=None):
+        """
+        Autoscale the axis view to the data (toggle).
+
+        Convenience method for simple axis view autoscaling.
+        It turns autoscaling on or off, and then,
+        if autoscaling for either axis is on, it performs
+        the autoscaling on the specified axis or axes.
+
+        Parameters
+        ----------
+        enable : bool or None, optional
+            True (default) turns autoscaling on, False turns it off.
+            None leaves the autoscaling state unchanged.
+
+        axis : {'both', 'x', 'y'}, optional
+            Which axis to operate on; default is 'both'.
+
+        tight : bool or None, optional
+            If True, first set the margins to zero.  Then, this argument is
+            forwarded to `autoscale_view` (regardless of its value); see the
+            description of its behavior there.
+        """
+        if enable is None:
+            scalex = True
+            scaley = True
+        else:
+            scalex = False
+            scaley = False
+            if axis in ['x', 'both']:
+                self._autoscaleXon = bool(enable)
+                scalex = self._autoscaleXon
+            if axis in ['y', 'both']:
+                self._autoscaleYon = bool(enable)
+                scaley = self._autoscaleYon
+        if tight and scalex:
+            self._xmargin = 0
+        if tight and scaley:
+            self._ymargin = 0
+        self._request_autoscale_view(tight=tight, scalex=scalex, scaley=scaley)
+
+    def autoscale_view(self, tight=None, scalex=True, scaley=True):
+        """
+        Autoscale the view limits using the data limits.
+
+        Parameters
+        ----------
+        tight : bool or None
+            If *True*, only expand the axis limits using the margins.  Note
+            that unlike for `autoscale`, ``tight=True`` does *not* set the
+            margins to zero.
+
+            If *False* and :rc:`axes.autolimit_mode` is 'round_numbers', then
+            after expansion by the margins, further expand the axis limits
+            using the axis major locator.
+
+            If None (the default), reuse the value set in the previous call to
+            `autoscale_view` (the initial value is False, but the default style
+            sets :rc:`axes.autolimit_mode` to 'data', in which case this
+            behaves like True).
+
+        scalex : bool
+            Whether to autoscale the x axis (default is True).
+
+        scaley : bool
+            Whether to autoscale the x axis (default is True).
+
+        Notes
+        -----
+        The autoscaling preserves any preexisting axis direction reversal.
+
+        The data limits are not updated automatically when artist data are
+        changed after the artist has been added to an Axes instance.  In that
+        case, use :meth:`matplotlib.axes.Axes.relim` prior to calling
+        autoscale_view.
+
+        If the views of the axes are fixed, e.g. via `set_xlim`, they will
+        not be changed by autoscale_view().
+        See :meth:`matplotlib.axes.Axes.autoscale` for an alternative.
+        """
+        if tight is not None:
+            self._tight = bool(tight)
+
+        x_stickies = y_stickies = np.array([])
+        if self.use_sticky_edges:
+            # Only iterate over axes and artists if needed.  The check for
+            # ``hasattr(ax, "lines")`` is necessary because this can be called
+            # very early in the axes init process (e.g., for twin axes) when
+            # these attributes don't even exist yet, in which case
+            # `get_children` would raise an AttributeError.
+            if self._xmargin and scalex and self._autoscaleXon:
+                x_stickies = np.sort(np.concatenate([
+                    artist.sticky_edges.x
+                    for ax in self._shared_x_axes.get_siblings(self)
+                    if hasattr(ax, "lines")
+                    for artist in ax.get_children()]))
+            if self._ymargin and scaley and self._autoscaleYon:
+                y_stickies = np.sort(np.concatenate([
+                    artist.sticky_edges.y
+                    for ax in self._shared_y_axes.get_siblings(self)
+                    if hasattr(ax, "lines")
+                    for artist in ax.get_children()]))
+        if self.get_xscale().lower() == 'log':
+            x_stickies = x_stickies[x_stickies > 0]
+        if self.get_yscale().lower() == 'log':
+            y_stickies = y_stickies[y_stickies > 0]
+
+        def handle_single_axis(scale, autoscaleon, shared_axes, interval,
+                               minpos, axis, margin, stickies, set_bound):
+
+            if not (scale and autoscaleon):
+                return  # nothing to do...
+
+            shared = shared_axes.get_siblings(self)
+            dl = [ax.dataLim for ax in shared]
+            # ignore non-finite data limits if good limits exist
+            finite_dl = [d for d in dl if np.isfinite(d).all()]
+            if len(finite_dl):
+                # if finite limits exist for atleast one axis (and the
+                # other is infinite), restore the finite limits
+                x_finite = [d for d in dl
+                            if (np.isfinite(d.intervalx).all() and
+                                (d not in finite_dl))]
+                y_finite = [d for d in dl
+                            if (np.isfinite(d.intervaly).all() and
+                                (d not in finite_dl))]
+
+                dl = finite_dl
+                dl.extend(x_finite)
+                dl.extend(y_finite)
+
+            bb = mtransforms.BboxBase.union(dl)
+            x0, x1 = getattr(bb, interval)
+            # If x0 and x1 are non finite, use the locator to figure out
+            # default limits.
+            locator = axis.get_major_locator()
+            x0, x1 = locator.nonsingular(x0, x1)
+
+            # Prevent margin addition from crossing a sticky value.  Small
+            # tolerances (whose values come from isclose()) must be used due to
+            # floating point issues with streamplot.
+            def tol(x): return 1e-5 * abs(x) + 1e-8
+            # Index of largest element < x0 + tol, if any.
+            i0 = stickies.searchsorted(x0 + tol(x0)) - 1
+            x0bound = stickies[i0] if i0 != -1 else None
+            # Index of smallest element > x1 - tol, if any.
+            i1 = stickies.searchsorted(x1 - tol(x1))
+            x1bound = stickies[i1] if i1 != len(stickies) else None
+
+            # Add the margin in figure space and then transform back, to handle
+            # non-linear scales.
+            minpos = getattr(bb, minpos)
+            transform = axis.get_transform()
+            inverse_trans = transform.inverted()
+            x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
+            x0t, x1t = transform.transform([x0, x1])
+            delta = (x1t - x0t) * margin
+            if not np.isfinite(delta):
+                delta = 0  # If a bound isn't finite, set margin to zero.
+            x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta])
+
+            # Apply sticky bounds.
+            if x0bound is not None:
+                x0 = max(x0, x0bound)
+            if x1bound is not None:
+                x1 = min(x1, x1bound)
+
+            if not self._tight:
+                x0, x1 = locator.view_limits(x0, x1)
+            set_bound(x0, x1)
+            # End of definition of internal function 'handle_single_axis'.
+
+        handle_single_axis(
+            scalex, self._autoscaleXon, self._shared_x_axes, 'intervalx',
+            'minposx', self.xaxis, self._xmargin, x_stickies, self.set_xbound)
+        handle_single_axis(
+            scaley, self._autoscaleYon, self._shared_y_axes, 'intervaly',
+            'minposy', self.yaxis, self._ymargin, y_stickies, self.set_ybound)
+
+    def _get_axis_list(self):
+        return (self.xaxis, self.yaxis)
+
+    def _get_axis_map(self):
+        """
+        Return a mapping of `Axis` "names" to `Axis` instances.
+
+        The `Axis` name is derived from the attribute under which the instance
+        is stored, so e.g. for polar axes, the theta-axis is still named "x"
+        and the r-axis is still named "y" (for back-compatibility).
+
+        In practice, this means that the entries are typically "x" and "y", and
+        additionally "z" for 3D axes.
+        """
+        d = {}
+        axis_list = self._get_axis_list()
+        for k, v in vars(self).items():
+            if k.endswith("axis") and v in axis_list:
+                d[k[:-len("axis")]] = v
+        return d
+
+    def _update_title_position(self, renderer):
+        """
+        Update the title position based on the bounding box enclosing
+        all the ticklabels and x-axis spine and xlabel...
+        """
+
+        if self._autotitlepos is not None and not self._autotitlepos:
+            _log.debug('title position was updated manually, not adjusting')
+            return
+
+        titles = (self.title, self._left_title, self._right_title)
+
+        if self._autotitlepos is None:
+            for title in titles:
+                x, y = title.get_position()
+                if not np.isclose(y, 1.0):
+                    self._autotitlepos = False
+                    _log.debug('not adjusting title pos because a title was'
+                             ' already placed manually: %f', y)
+                    return
+            self._autotitlepos = True
+
+        for title in titles:
+            x, _ = title.get_position()
+            # need to start again in case of window resizing
+            title.set_position((x, 1.0))
+            # need to check all our twins too...
+            axs = self._twinned_axes.get_siblings(self)
+            # and all the children
+            for ax in self.child_axes:
+                if ax is not None:
+                    locator = ax.get_axes_locator()
+                    if locator:
+                        pos = locator(self, renderer)
+                        ax.apply_aspect(pos)
+                    else:
+                        ax.apply_aspect()
+                    axs = axs + [ax]
+            top = 0
+            for ax in axs:
+                if (ax.xaxis.get_ticks_position() in ['top', 'unknown']
+                        or ax.xaxis.get_label_position() == 'top'):
+                    bb = ax.xaxis.get_tightbbox(renderer)
+                else:
+                    bb = ax.get_window_extent(renderer)
+                if bb is not None:
+                    top = max(top, bb.ymax)
+            if title.get_window_extent(renderer).ymin < top:
+                _, y = self.transAxes.inverted().transform((0, top))
+                title.set_position((x, y))
+                # empirically, this doesn't always get the min to top,
+                # so we need to adjust again.
+                if title.get_window_extent(renderer).ymin < top:
+                    _, y = self.transAxes.inverted().transform(
+                        (0., 2 * top - title.get_window_extent(renderer).ymin))
+                    title.set_position((x, y))
+
+        ymax = max(title.get_position()[1] for title in titles)
+        for title in titles:
+            # now line up all the titles at the highest baseline.
+            x, _ = title.get_position()
+            title.set_position((x, ymax))
+
+    # Drawing
+    @martist.allow_rasterization
+    def draw(self, renderer=None, inframe=False):
+        """Draw everything (plot lines, axes, labels)"""
+        if renderer is None:
+            renderer = self.figure._cachedRenderer
+        if renderer is None:
+            raise RuntimeError('No renderer defined')
+        if not self.get_visible():
+            return
+        self._unstale_viewLim()
+
+        renderer.open_group('axes', gid=self.get_gid())
+
+        # prevent triggering call backs during the draw process
+        self._stale = True
+
+        # loop over self and child axes...
+        locator = self.get_axes_locator()
+        if locator:
+            pos = locator(self, renderer)
+            self.apply_aspect(pos)
+        else:
+            self.apply_aspect()
+
+        artists = self.get_children()
+        artists.remove(self.patch)
+
+        # the frame draws the edges around the axes patch -- we
+        # decouple these so the patch can be in the background and the
+        # frame in the foreground. Do this before drawing the axis
+        # objects so that the spine has the opportunity to update them.
+        if not (self.axison and self._frameon):
+            for spine in self.spines.values():
+                artists.remove(spine)
+
+        self._update_title_position(renderer)
+
+        if not self.axison or inframe:
+            for _axis in self._get_axis_list():
+                artists.remove(_axis)
+
+        if inframe:
+            artists.remove(self.title)
+            artists.remove(self._left_title)
+            artists.remove(self._right_title)
+
+        if not self.figure.canvas.is_saving():
+            artists = [a for a in artists
+                       if not a.get_animated() or a in self.images]
+        artists = sorted(artists, key=attrgetter('zorder'))
+
+        # rasterize artists with negative zorder
+        # if the minimum zorder is negative, start rasterization
+        rasterization_zorder = self._rasterization_zorder
+
+        if (rasterization_zorder is not None and
+                artists and artists[0].zorder < rasterization_zorder):
+            renderer.start_rasterizing()
+            artists_rasterized = [a for a in artists
+                                  if a.zorder < rasterization_zorder]
+            artists = [a for a in artists
+                       if a.zorder >= rasterization_zorder]
+        else:
+            artists_rasterized = []
+
+        # the patch draws the background rectangle -- the frame below
+        # will draw the edges
+        if self.axison and self._frameon:
+            self.patch.draw(renderer)
+
+        if artists_rasterized:
+            for a in artists_rasterized:
+                a.draw(renderer)
+            renderer.stop_rasterizing()
+
+        mimage._draw_list_compositing_images(renderer, self, artists)
+
+        renderer.close_group('axes')
+        self.stale = False
+
+    def draw_artist(self, a):
+        """
+        This method can only be used after an initial draw which
+        caches the renderer.  It is used to efficiently update Axes
+        data (axis ticks, labels, etc are not updated)
+        """
+        if self.figure._cachedRenderer is None:
+            raise AttributeError("draw_artist can only be used after an "
+                                 "initial draw which caches the renderer")
+        a.draw(self.figure._cachedRenderer)
+
+    def redraw_in_frame(self):
+        """
+        This method can only be used after an initial draw which
+        caches the renderer.  It is used to efficiently update Axes
+        data (axis ticks, labels, etc are not updated)
+        """
+        if self.figure._cachedRenderer is None:
+            raise AttributeError("redraw_in_frame can only be used after an "
+                                 "initial draw which caches the renderer")
+        self.draw(self.figure._cachedRenderer, inframe=True)
+
+    def get_renderer_cache(self):
+        return self.figure._cachedRenderer
+
+    # Axes rectangle characteristics
+
+    def get_frame_on(self):
+        """Get whether the axes rectangle patch is drawn."""
+        return self._frameon
+
+    def set_frame_on(self, b):
+        """
+        Set whether the axes rectangle patch is drawn.
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._frameon = b
+        self.stale = True
+
+    def get_axisbelow(self):
+        """
+        Get whether axis ticks and gridlines are above or below most artists.
+
+        Returns
+        -------
+        axisbelow : bool or 'line'
+
+        See Also
+        --------
+        set_axisbelow
+        """
+        return self._axisbelow
+
+    def set_axisbelow(self, b):
+        """
+        Set whether axis ticks and gridlines are above or below most artists.
+
+        This controls the zorder of the ticks and gridlines. For more
+        information on the zorder see :doc:`/gallery/misc/zorder_demo`.
+
+        Parameters
+        ----------
+        b : bool or 'line'
+            Possible values:
+
+            - *True* (zorder = 0.5): Ticks and gridlines are below all Artists.
+            - 'line' (zorder = 1.5): Ticks and gridlines are above patches
+              (e.g. rectangles, with default zorder = 1) but still below lines
+              and markers (with their default zorder = 2).
+            - *False* (zorder = 2.5): Ticks and gridlines are above patches
+              and lines / markers.
+
+        See Also
+        --------
+        get_axisbelow
+        """
+        self._axisbelow = axisbelow = validate_axisbelow(b)
+        if axisbelow is True:
+            zorder = 0.5
+        elif axisbelow is False:
+            zorder = 2.5
+        elif axisbelow == "line":
+            zorder = 1.5
+        else:
+            raise ValueError("Unexpected axisbelow value")
+        for axis in self._get_axis_list():
+            axis.set_zorder(zorder)
+        self.stale = True
+
+    @docstring.dedent_interpd
+    def grid(self, b=None, which='major', axis='both', **kwargs):
+        """
+        Configure the grid lines.
+
+        Parameters
+        ----------
+        b : bool or None, optional
+            Whether to show the grid lines. If any *kwargs* are supplied,
+            it is assumed you want the grid on and *b* will be set to True.
+
+            If *b* is *None* and there are no *kwargs*, this toggles the
+            visibility of the lines.
+
+        which : {'major', 'minor', 'both'}, optional
+            The grid lines to apply the changes on.
+
+        axis : {'both', 'x', 'y'}, optional
+            The axis to apply the changes on.
+
+        **kwargs : `.Line2D` properties
+            Define the line properties of the grid, e.g.::
+
+                grid(color='r', linestyle='-', linewidth=2)
+
+            Valid keyword arguments are:
+
+            %(_Line2D_docstr)s
+
+        Notes
+        -----
+        The axis is drawn as a unit, so the effective zorder for drawing the
+        grid is determined by the zorder of each axis, not by the zorder of the
+        `.Line2D` objects comprising the grid.  Therefore, to set grid zorder,
+        use `.set_axisbelow` or, for more control, call the
+        `~matplotlib.axis.Axis.set_zorder` method of each axis.
+        """
+        if len(kwargs):
+            b = True
+        cbook._check_in_list(['x', 'y', 'both'], axis=axis)
+        if axis in ['x', 'both']:
+            self.xaxis.grid(b, which=which, **kwargs)
+        if axis in ['y', 'both']:
+            self.yaxis.grid(b, which=which, **kwargs)
+
+    def ticklabel_format(self, *, axis='both', style='', scilimits=None,
+                         useOffset=None, useLocale=None, useMathText=None):
+        r"""
+        Change the `~matplotlib.ticker.ScalarFormatter` used by
+        default for linear axes.
+
+        Optional keyword arguments:
+
+          ==============   =========================================
+          Keyword          Description
+          ==============   =========================================
+          *axis*           {'x', 'y', 'both'}
+          *style*          {'sci' (or 'scientific'), 'plain'}
+                           plain turns off scientific notation
+          *scilimits*      (m, n), pair of integers; if *style*
+                           is 'sci', scientific notation will
+                           be used for numbers outside the range
+                           10\ :sup:`m` to 10\ :sup:`n`.
+                           Use (0, 0) to include all numbers.
+                           Use (m, m) where m != 0 to fix the order
+                           of magnitude to 10\ :sup:`m`.
+          *useOffset*      bool or float
+                           If True, the offset will be calculated as
+                           needed; if False, no offset will be used;
+                           if a numeric offset is specified, it will
+                           be used.
+          *useLocale*      If True, format the number according to
+                           the current locale.  This affects things
+                           such as the character used for the
+                           decimal separator.  If False, use
+                           C-style (English) formatting.  The
+                           default setting is controlled by the
+                           axes.formatter.use_locale rcparam.
+          *useMathText*    If True, render the offset and scientific
+                           notation in mathtext
+          ==============   =========================================
+
+        Only the major ticks are affected.
+        If the method is called when the `~matplotlib.ticker.ScalarFormatter`
+        is not the `~matplotlib.ticker.Formatter` being used, an
+        `AttributeError` will be raised.
+        """
+        style = style.lower()
+        axis = axis.lower()
+        if scilimits is not None:
+            try:
+                m, n = scilimits
+                m + n + 1  # check that both are numbers
+            except (ValueError, TypeError):
+                raise ValueError("scilimits must be a sequence of 2 integers")
+        STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None}
+        is_sci_style = cbook._check_getitem(STYLES, style=style)
+        axis_map = {**{k: [v] for k, v in self._get_axis_map().items()},
+                    'both': self._get_axis_list()}
+        axises = cbook._check_getitem(axis_map, axis=axis)
+        try:
+            for axis in axises:
+                if is_sci_style is not None:
+                    axis.major.formatter.set_scientific(is_sci_style)
+                if scilimits is not None:
+                    axis.major.formatter.set_powerlimits(scilimits)
+                if useOffset is not None:
+                    axis.major.formatter.set_useOffset(useOffset)
+                if useLocale is not None:
+                    axis.major.formatter.set_useLocale(useLocale)
+                if useMathText is not None:
+                    axis.major.formatter.set_useMathText(useMathText)
+        except AttributeError:
+            raise AttributeError(
+                "This method only works with the ScalarFormatter")
+
+    def locator_params(self, axis='both', tight=None, **kwargs):
+        """
+        Control behavior of major tick locators.
+
+        Because the locator is involved in autoscaling, `~.Axes.autoscale_view`
+        is called automatically after the parameters are changed.
+
+        Parameters
+        ----------
+        axis : {'both', 'x', 'y'}, optional
+            The axis on which to operate.
+
+        tight : bool or None, optional
+            Parameter passed to `~.Axes.autoscale_view`.
+            Default is None, for no change.
+
+        Other Parameters
+        ----------------
+        **kwargs
+            Remaining keyword arguments are passed to directly to the
+            ``set_params()`` method of the locator. Supported keywords depend
+            on the type of the locator. See for example
+            `~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator`
+            used by default for linear axes.
+
+        Examples
+        --------
+        When plotting small subplots, one might want to reduce the maximum
+        number of ticks and use tight bounds, for example::
+
+            ax.locator_params(tight=True, nbins=4)
+
+        """
+        _x = axis in ['x', 'both']
+        _y = axis in ['y', 'both']
+        if _x:
+            self.xaxis.get_major_locator().set_params(**kwargs)
+        if _y:
+            self.yaxis.get_major_locator().set_params(**kwargs)
+        self._request_autoscale_view(tight=tight, scalex=_x, scaley=_y)
+
+    def tick_params(self, axis='both', **kwargs):
+        """Change the appearance of ticks, tick labels, and gridlines.
+
+        Parameters
+        ----------
+        axis : {'x', 'y', 'both'}, optional
+            Which axis to apply the parameters to.
+
+        Other Parameters
+        ----------------
+        axis : {'x', 'y', 'both'}
+            Axis on which to operate; default is 'both'.
+        reset : bool, default: False
+            If *True*, set all parameters to defaults before processing other
+            keyword arguments.
+        which : {'major', 'minor', 'both'}
+            Default is 'major'; apply arguments to *which* ticks.
+        direction : {'in', 'out', 'inout'}
+            Puts ticks inside the axes, outside the axes, or both.
+        length : float
+            Tick length in points.
+        width : float
+            Tick width in points.
+        color : color
+            Tick color.
+        pad : float
+            Distance in points between tick and label.
+        labelsize : float or str
+            Tick label font size in points or as a string (e.g., 'large').
+        labelcolor : color
+            Tick label color.
+        colors : color
+            Tick color and label color.
+        zorder : float
+            Tick and label zorder.
+        bottom, top, left, right : bool
+            Whether to draw the respective ticks.
+        labelbottom, labeltop, labelleft, labelright : bool
+            Whether to draw the respective tick labels.
+        labelrotation : float
+            Tick label rotation
+        grid_color : color
+            Gridline color.
+        grid_alpha : float
+            Transparency of gridlines: 0 (transparent) to 1 (opaque).
+        grid_linewidth : float
+            Width of gridlines in points.
+        grid_linestyle : str
+            Any valid `.Line2D` line style spec.
+
+        Examples
+        --------
+        Usage ::
+
+            ax.tick_params(direction='out', length=6, width=2, colors='r',
+                           grid_color='r', grid_alpha=0.5)
+
+        This will make all major ticks be red, pointing out of the box,
+        and with dimensions 6 points by 2 points.  Tick labels will
+        also be red.  Gridlines will be red and translucent.
+
+        """
+        cbook._check_in_list(['x', 'y', 'both'], axis=axis)
+        if axis in ['x', 'both']:
+            xkw = dict(kwargs)
+            xkw.pop('left', None)
+            xkw.pop('right', None)
+            xkw.pop('labelleft', None)
+            xkw.pop('labelright', None)
+            self.xaxis.set_tick_params(**xkw)
+        if axis in ['y', 'both']:
+            ykw = dict(kwargs)
+            ykw.pop('top', None)
+            ykw.pop('bottom', None)
+            ykw.pop('labeltop', None)
+            ykw.pop('labelbottom', None)
+            self.yaxis.set_tick_params(**ykw)
+
+    def set_axis_off(self):
+        """
+        Turn the x- and y-axis off.
+
+        This affects the axis lines, ticks, ticklabels, grid and axis labels.
+        """
+        self.axison = False
+        self.stale = True
+
+    def set_axis_on(self):
+        """
+        Turn the x- and y-axis on.
+
+        This affects the axis lines, ticks, ticklabels, grid and axis labels.
+        """
+        self.axison = True
+        self.stale = True
+
+    # data limits, ticks, tick labels, and formatting
+
+    def invert_xaxis(self):
+        """
+        Invert the x-axis.
+
+        See Also
+        --------
+        xaxis_inverted
+        get_xlim, set_xlim
+        get_xbound, set_xbound
+        """
+        self.xaxis.set_inverted(not self.xaxis.get_inverted())
+
+    def xaxis_inverted(self):
+        """
+        Return whether the x-axis is inverted.
+
+        The axis is inverted if the left value is larger than the right value.
+
+        See Also
+        --------
+        invert_xaxis
+        get_xlim, set_xlim
+        get_xbound, set_xbound
+        """
+        return self.xaxis.get_inverted()
+
+    def get_xbound(self):
+        """
+        Return the lower and upper x-axis bounds, in increasing order.
+
+        See Also
+        --------
+        set_xbound
+        get_xlim, set_xlim
+        invert_xaxis, xaxis_inverted
+        """
+        left, right = self.get_xlim()
+        if left < right:
+            return left, right
+        else:
+            return right, left
+
+    def set_xbound(self, lower=None, upper=None):
+        """
+        Set the lower and upper numerical bounds of the x-axis.
+
+        This method will honor axes inversion regardless of parameter order.
+        It will not change the autoscaling setting (``Axes._autoscaleXon``).
+
+        Parameters
+        ----------
+        lower, upper : float or None
+            The lower and upper bounds. If *None*, the respective axis bound
+            is not modified.
+
+        See Also
+        --------
+        get_xbound
+        get_xlim, set_xlim
+        invert_xaxis, xaxis_inverted
+        """
+        if upper is None and np.iterable(lower):
+            lower, upper = lower
+
+        old_lower, old_upper = self.get_xbound()
+
+        if lower is None:
+            lower = old_lower
+        if upper is None:
+            upper = old_upper
+
+        if self.xaxis_inverted():
+            if lower < upper:
+                self.set_xlim(upper, lower, auto=None)
+            else:
+                self.set_xlim(lower, upper, auto=None)
+        else:
+            if lower < upper:
+                self.set_xlim(lower, upper, auto=None)
+            else:
+                self.set_xlim(upper, lower, auto=None)
+
+    def get_xlim(self):
+        """
+        Return the x-axis view limits.
+
+        Returns
+        -------
+        left, right : (float, float)
+            The current x-axis limits in data coordinates.
+
+        See Also
+        --------
+        set_xlim
+        set_xbound, get_xbound
+        invert_xaxis, xaxis_inverted
+
+        Notes
+        -----
+        The x-axis may be inverted, in which case the *left* value will
+        be greater than the *right* value.
+
+        """
+        return tuple(self.viewLim.intervalx)
+
+    def _validate_converted_limits(self, limit, convert):
+        """
+        Raise ValueError if converted limits are non-finite.
+
+        Note that this function also accepts None as a limit argument.
+
+        Returns
+        -------
+        The limit value after call to convert(), or None if limit is None.
+        """
+        if limit is not None:
+            converted_limit = convert(limit)
+            if (isinstance(converted_limit, Real)
+                    and not np.isfinite(converted_limit)):
+                raise ValueError("Axis limits cannot be NaN or Inf")
+            return converted_limit
+
+    def set_xlim(self, left=None, right=None, emit=True, auto=False,
+                 *, xmin=None, xmax=None):
+        """
+        Set the x-axis view limits.
+
+        Parameters
+        ----------
+        left : float, optional
+            The left xlim in data coordinates. Passing *None* leaves the
+            limit unchanged.
+
+            The left and right xlims may also be passed as the tuple
+            (*left*, *right*) as the first positional argument (or as
+            the *left* keyword argument).
+
+            .. ACCEPTS: (bottom: float, top: float)
+
+        right : float, optional
+            The right xlim in data coordinates. Passing *None* leaves the
+            limit unchanged.
+
+        emit : bool, optional
+            Whether to notify observers of limit change (default: True).
+
+        auto : bool or None, optional
+            Whether to turn on autoscaling of the x-axis. True turns on,
+            False turns off (default action), None leaves unchanged.
+
+        xmin, xmax : scalar, optional
+            They are equivalent to left and right respectively,
+            and it is an error to pass both *xmin* and *left* or
+            *xmax* and *right*.
+
+        Returns
+        -------
+        left, right : (float, float)
+            The new x-axis limits in data coordinates.
+
+        See Also
+        --------
+        get_xlim
+        set_xbound, get_xbound
+        invert_xaxis, xaxis_inverted
+
+        Notes
+        -----
+        The *left* value may be greater than the *right* value, in which
+        case the x-axis values will decrease from left to right.
+
+        Examples
+        --------
+        >>> set_xlim(left, right)
+        >>> set_xlim((left, right))
+        >>> left, right = set_xlim(left, right)
+
+        One limit may be left unchanged.
+
+        >>> set_xlim(right=right_lim)
+
+        Limits may be passed in reverse order to flip the direction of
+        the x-axis. For example, suppose *x* represents the number of
+        years before present. The x-axis limits might be set like the
+        following so 5000 years ago is on the left of the plot and the
+        present is on the right.
+
+        >>> set_xlim(5000, 0)
+
+        """
+        if right is None and np.iterable(left):
+            left, right = left
+        if xmin is not None:
+            if left is not None:
+                raise TypeError('Cannot pass both `xmin` and `left`')
+            left = xmin
+        if xmax is not None:
+            if right is not None:
+                raise TypeError('Cannot pass both `xmax` and `right`')
+            right = xmax
+
+        self._process_unit_info(xdata=(left, right))
+        left = self._validate_converted_limits(left, self.convert_xunits)
+        right = self._validate_converted_limits(right, self.convert_xunits)
+
+        if left is None or right is None:
+            # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
+            # so only grab the limits if we really need them.
+            old_left, old_right = self.get_xlim()
+            if left is None:
+                left = old_left
+            if right is None:
+                right = old_right
+
+        if self.get_xscale() == 'log' and (left <= 0 or right <= 0):
+            # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
+            # so only grab the limits if we really need them.
+            old_left, old_right = self.get_xlim()
+            if left <= 0:
+                cbook._warn_external(
+                    'Attempted to set non-positive left xlim on a '
+                    'log-scaled axis.\n'
+                    'Invalid limit will be ignored.')
+                left = old_left
+            if right <= 0:
+                cbook._warn_external(
+                    'Attempted to set non-positive right xlim on a '
+                    'log-scaled axis.\n'
+                    'Invalid limit will be ignored.')
+                right = old_right
+        if left == right:
+            cbook._warn_external(
+                f"Attempting to set identical left == right == {left} results "
+                f"in singular transformations; automatically expanding.")
+        reverse = left > right
+        left, right = self.xaxis.get_major_locator().nonsingular(left, right)
+        left, right = self.xaxis.limit_range_for_scale(left, right)
+        # cast to bool to avoid bad interaction between python 3.8 and np.bool_
+        left, right = sorted([left, right], reverse=bool(reverse))
+
+        self._viewLim.intervalx = (left, right)
+        if auto is not None:
+            self._autoscaleXon = bool(auto)
+
+        if emit:
+            self.callbacks.process('xlim_changed', self)
+            # Call all of the other x-axes that are shared with this one
+            for other in self._shared_x_axes.get_siblings(self):
+                if other is not self:
+                    other.set_xlim(self.viewLim.intervalx,
+                                   emit=False, auto=auto)
+                    if other.figure != self.figure:
+                        other.figure.canvas.draw_idle()
+        self.stale = True
+        return left, right
+
+    def get_xscale(self):
+        """
+        Return the x-axis scale as string.
+
+        See Also
+        --------
+        set_xscale
+        """
+        return self.xaxis.get_scale()
+
+    def set_xscale(self, value, **kwargs):
+        """
+        Set the x-axis scale.
+
+        Parameters
+        ----------
+        value : {"linear", "log", "symlog", "logit", ...}
+            The axis scale type to apply.
+
+        **kwargs
+            Different keyword arguments are accepted, depending on the scale.
+            See the respective class keyword arguments:
+
+            - `matplotlib.scale.LinearScale`
+            - `matplotlib.scale.LogScale`
+            - `matplotlib.scale.SymmetricalLogScale`
+            - `matplotlib.scale.LogitScale`
+
+        Notes
+        -----
+        By default, Matplotlib supports the above mentioned scales.
+        Additionally, custom scales may be registered using
+        `matplotlib.scale.register_scale`. These scales can then also
+        be used here.
+        """
+        old_default_lims = (self.xaxis.get_major_locator()
+                            .nonsingular(-np.inf, np.inf))
+        g = self.get_shared_x_axes()
+        for ax in g.get_siblings(self):
+            ax.xaxis._set_scale(value, **kwargs)
+            ax._update_transScale()
+            ax.stale = True
+        new_default_lims = (self.xaxis.get_major_locator()
+                            .nonsingular(-np.inf, np.inf))
+        if old_default_lims != new_default_lims:
+            # Force autoscaling now, to take advantage of the scale locator's
+            # nonsingular() before it possibly gets swapped out by the user.
+            self.autoscale_view(scaley=False)
+
+    @cbook._make_keyword_only("3.2", "minor")
+    def get_xticks(self, minor=False):
+        """Return the x ticks as a list of locations"""
+        return self.xaxis.get_ticklocs(minor=minor)
+
+    @cbook._make_keyword_only("3.2", "minor")
+    def set_xticks(self, ticks, minor=False):
+        """
+        Set the x ticks with list of *ticks*
+
+        Parameters
+        ----------
+        ticks : list
+            List of x-axis tick locations.
+
+        minor : bool, optional
+            If ``False`` sets major ticks, if ``True`` sets minor ticks.
+            Default is ``False``.
+        """
+        ret = self.xaxis.set_ticks(ticks, minor=minor)
+        self.stale = True
+        return ret
+
+    def get_xmajorticklabels(self):
+        """
+        Get the major x tick labels.
+
+        Returns
+        -------
+        labels : list
+            List of `~matplotlib.text.Text` instances
+        """
+        return self.xaxis.get_majorticklabels()
+
+    def get_xminorticklabels(self):
+        """
+        Get the minor x tick labels.
+
+        Returns
+        -------
+        labels : list
+            List of `~matplotlib.text.Text` instances
+        """
+        return self.xaxis.get_minorticklabels()
+
+    def get_xticklabels(self, minor=False, which=None):
+        """
+        Get the x tick labels as a list of `~matplotlib.text.Text` instances.
+
+        Parameters
+        ----------
+        minor : bool, optional
+           If True return the minor ticklabels,
+           else return the major ticklabels.
+
+        which : None, ('minor', 'major', 'both')
+           Overrides *minor*.
+
+           Selects which ticklabels to return
+
+        Returns
+        -------
+        ret : list
+           List of `~matplotlib.text.Text` instances.
+        """
+        return self.xaxis.get_ticklabels(minor=minor, which=which)
+
+    def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs):
+        """
+        Set the x-tick labels with list of string labels.
+
+        Parameters
+        ----------
+        labels : List[str]
+            List of string labels.
+
+        fontdict : dict, optional
+            A dictionary controlling the appearance of the ticklabels.
+            The default *fontdict* is::
+
+               {'fontsize': rcParams['axes.titlesize'],
+                'fontweight': rcParams['axes.titleweight'],
+                'verticalalignment': 'baseline',
+                'horizontalalignment': loc}
+
+        minor : bool, optional
+            Whether to set the minor ticklabels rather than the major ones.
+
+        Returns
+        -------
+        A list of `~.text.Text` instances.
+
+        Other Parameters
+        -----------------
+        **kwargs : `~.text.Text` properties.
+        """
+        if fontdict is not None:
+            kwargs.update(fontdict)
+        ret = self.xaxis.set_ticklabels(labels,
+                                        minor=minor, **kwargs)
+        self.stale = True
+        return ret
+
+    def invert_yaxis(self):
+        """
+        Invert the y-axis.
+
+        See Also
+        --------
+        yaxis_inverted
+        get_ylim, set_ylim
+        get_ybound, set_ybound
+        """
+        self.yaxis.set_inverted(not self.yaxis.get_inverted())
+
+    def yaxis_inverted(self):
+        """
+        Return whether the y-axis is inverted.
+
+        The axis is inverted if the bottom value is larger than the top value.
+
+        See Also
+        --------
+        invert_yaxis
+        get_ylim, set_ylim
+        get_ybound, set_ybound
+        """
+        return self.yaxis.get_inverted()
+
+    def get_ybound(self):
+        """
+        Return the lower and upper y-axis bounds, in increasing order.
+
+        See Also
+        --------
+        set_ybound
+        get_ylim, set_ylim
+        invert_yaxis, yaxis_inverted
+        """
+        bottom, top = self.get_ylim()
+        if bottom < top:
+            return bottom, top
+        else:
+            return top, bottom
+
+    def set_ybound(self, lower=None, upper=None):
+        """
+        Set the lower and upper numerical bounds of the y-axis.
+
+        This method will honor axes inversion regardless of parameter order.
+        It will not change the autoscaling setting (``Axes._autoscaleYon``).
+
+        Parameters
+        ----------
+        lower, upper : float or None
+            The lower and upper bounds. If *None*, the respective axis bound
+            is not modified.
+
+        See Also
+        --------
+        get_ybound
+        get_ylim, set_ylim
+        invert_yaxis, yaxis_inverted
+        """
+        if upper is None and np.iterable(lower):
+            lower, upper = lower
+
+        old_lower, old_upper = self.get_ybound()
+
+        if lower is None:
+            lower = old_lower
+        if upper is None:
+            upper = old_upper
+
+        if self.yaxis_inverted():
+            if lower < upper:
+                self.set_ylim(upper, lower, auto=None)
+            else:
+                self.set_ylim(lower, upper, auto=None)
+        else:
+            if lower < upper:
+                self.set_ylim(lower, upper, auto=None)
+            else:
+                self.set_ylim(upper, lower, auto=None)
+
+    def get_ylim(self):
+        """
+        Return the y-axis view limits.
+
+        Returns
+        -------
+        bottom, top : (float, float)
+            The current y-axis limits in data coordinates.
+
+        See Also
+        --------
+        set_ylim
+        set_ybound, get_ybound
+        invert_yaxis, yaxis_inverted
+
+        Notes
+        -----
+        The y-axis may be inverted, in which case the *bottom* value
+        will be greater than the *top* value.
+
+        """
+        return tuple(self.viewLim.intervaly)
+
+    def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
+                 *, ymin=None, ymax=None):
+        """
+        Set the y-axis view limits.
+
+        Parameters
+        ----------
+        bottom : float, optional
+            The bottom ylim in data coordinates. Passing *None* leaves the
+            limit unchanged.
+
+            The bottom and top ylims may also be passed as the tuple
+            (*bottom*, *top*) as the first positional argument (or as
+            the *bottom* keyword argument).
+
+            .. ACCEPTS: (bottom: float, top: float)
+
+        top : float, optional
+            The top ylim in data coordinates. Passing *None* leaves the
+            limit unchanged.
+
+        emit : bool, optional
+            Whether to notify observers of limit change (default: ``True``).
+
+        auto : bool or None, optional
+            Whether to turn on autoscaling of the y-axis. *True* turns on,
+            *False* turns off (default action), *None* leaves unchanged.
+
+        ymin, ymax : scalar, optional
+            They are equivalent to bottom and top respectively,
+            and it is an error to pass both *ymin* and *bottom* or
+            *ymax* and *top*.
+
+        Returns
+        -------
+        bottom, top : (float, float)
+            The new y-axis limits in data coordinates.
+
+        See Also
+        --------
+        get_ylim
+        set_ybound, get_ybound
+        invert_yaxis, yaxis_inverted
+
+        Notes
+        -----
+        The *bottom* value may be greater than the *top* value, in which
+        case the y-axis values will decrease from *bottom* to *top*.
+
+        Examples
+        --------
+        >>> set_ylim(bottom, top)
+        >>> set_ylim((bottom, top))
+        >>> bottom, top = set_ylim(bottom, top)
+
+        One limit may be left unchanged.
+
+        >>> set_ylim(top=top_lim)
+
+        Limits may be passed in reverse order to flip the direction of
+        the y-axis. For example, suppose ``y`` represents depth of the
+        ocean in m. The y-axis limits might be set like the following
+        so 5000 m depth is at the bottom of the plot and the surface,
+        0 m, is at the top.
+
+        >>> set_ylim(5000, 0)
+        """
+        if top is None and np.iterable(bottom):
+            bottom, top = bottom
+        if ymin is not None:
+            if bottom is not None:
+                raise TypeError('Cannot pass both `ymin` and `bottom`')
+            bottom = ymin
+        if ymax is not None:
+            if top is not None:
+                raise TypeError('Cannot pass both `ymax` and `top`')
+            top = ymax
+
+        self._process_unit_info(ydata=(bottom, top))
+        bottom = self._validate_converted_limits(bottom, self.convert_yunits)
+        top = self._validate_converted_limits(top, self.convert_yunits)
+
+        if bottom is None or top is None:
+            # Axes init calls set_ylim(0, 1) before get_ylim() can be called,
+            # so only grab the limits if we really need them.
+            old_bottom, old_top = self.get_ylim()
+            if bottom is None:
+                bottom = old_bottom
+            if top is None:
+                top = old_top
+
+        if self.get_yscale() == 'log' and (bottom <= 0 or top <= 0):
+            # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
+            # so only grab the limits if we really need them.
+            old_bottom, old_top = self.get_ylim()
+            if bottom <= 0:
+                cbook._warn_external(
+                    'Attempted to set non-positive bottom ylim on a '
+                    'log-scaled axis.\n'
+                    'Invalid limit will be ignored.')
+                bottom = old_bottom
+            if top <= 0:
+                cbook._warn_external(
+                    'Attempted to set non-positive top ylim on a '
+                    'log-scaled axis.\n'
+                    'Invalid limit will be ignored.')
+                top = old_top
+        if bottom == top:
+            cbook._warn_external(
+                f"Attempting to set identical bottom == top == {bottom} "
+                f"results in singular transformations; automatically "
+                f"expanding.")
+        reverse = bottom > top
+        bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
+        bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
+        # cast to bool to avoid bad interaction between python 3.8 and np.bool_
+        bottom, top = sorted([bottom, top], reverse=bool(reverse))
+
+        self._viewLim.intervaly = (bottom, top)
+        if auto is not None:
+            self._autoscaleYon = bool(auto)
+
+        if emit:
+            self.callbacks.process('ylim_changed', self)
+            # Call all of the other y-axes that are shared with this one
+            for other in self._shared_y_axes.get_siblings(self):
+                if other is not self:
+                    other.set_ylim(self.viewLim.intervaly,
+                                   emit=False, auto=auto)
+                    if other.figure != self.figure:
+                        other.figure.canvas.draw_idle()
+        self.stale = True
+        return bottom, top
+
+    def get_yscale(self):
+        """
+        Return the y-axis scale as string.
+
+        See Also
+        --------
+        set_yscale
+        """
+        return self.yaxis.get_scale()
+
+    def set_yscale(self, value, **kwargs):
+        """
+        Set the y-axis scale.
+
+        Parameters
+        ----------
+        value : {"linear", "log", "symlog", "logit", ...}
+            The axis scale type to apply.
+
+        **kwargs
+            Different keyword arguments are accepted, depending on the scale.
+            See the respective class keyword arguments:
+
+            - `matplotlib.scale.LinearScale`
+            - `matplotlib.scale.LogScale`
+            - `matplotlib.scale.SymmetricalLogScale`
+            - `matplotlib.scale.LogitScale`
+
+        Notes
+        -----
+        By default, Matplotlib supports the above mentioned scales.
+        Additionally, custom scales may be registered using
+        `matplotlib.scale.register_scale`. These scales can then also
+        be used here.
+        """
+        old_default_lims = (self.yaxis.get_major_locator()
+                            .nonsingular(-np.inf, np.inf))
+        g = self.get_shared_y_axes()
+        for ax in g.get_siblings(self):
+            ax.yaxis._set_scale(value, **kwargs)
+            ax._update_transScale()
+            ax.stale = True
+        new_default_lims = (self.yaxis.get_major_locator()
+                            .nonsingular(-np.inf, np.inf))
+        if old_default_lims != new_default_lims:
+            # Force autoscaling now, to take advantage of the scale locator's
+            # nonsingular() before it possibly gets swapped out by the user.
+            self.autoscale_view(scalex=False)
+
+    @cbook._make_keyword_only("3.2", "minor")
+    def get_yticks(self, minor=False):
+        """Return the y ticks as a list of locations"""
+        return self.yaxis.get_ticklocs(minor=minor)
+
+    @cbook._make_keyword_only("3.2", "minor")
+    def set_yticks(self, ticks, minor=False):
+        """
+        Set the y ticks with list of *ticks*
+
+        Parameters
+        ----------
+        ticks : list
+            List of y-axis tick locations
+
+        minor : bool, optional
+            If ``False`` sets major ticks, if ``True`` sets minor ticks.
+            Default is ``False``.
+        """
+        ret = self.yaxis.set_ticks(ticks, minor=minor)
+        return ret
+
+    def get_ymajorticklabels(self):
+        """
+        Get the major y tick labels.
+
+        Returns
+        -------
+        labels : list
+            List of `~matplotlib.text.Text` instances
+        """
+        return self.yaxis.get_majorticklabels()
+
+    def get_yminorticklabels(self):
+        """
+        Get the minor y tick labels.
+
+        Returns
+        -------
+        labels : list
+            List of `~matplotlib.text.Text` instances
+        """
+        return self.yaxis.get_minorticklabels()
+
+    def get_yticklabels(self, minor=False, which=None):
+        """
+        Get the y tick labels as a list of `~matplotlib.text.Text` instances.
+
+        Parameters
+        ----------
+        minor : bool
+           If True return the minor ticklabels,
+           else return the major ticklabels
+
+        which : None, ('minor', 'major', 'both')
+           Overrides *minor*.
+
+           Selects which ticklabels to return
+
+        Returns
+        -------
+        ret : list
+           List of `~matplotlib.text.Text` instances.
+        """
+        return self.yaxis.get_ticklabels(minor=minor, which=which)
+
+    def set_yticklabels(self, labels, fontdict=None, minor=False, **kwargs):
+        """
+        Set the y-tick labels with list of strings labels.
+
+        Parameters
+        ----------
+        labels : List[str]
+            list of string labels
+
+        fontdict : dict, optional
+            A dictionary controlling the appearance of the ticklabels.
+            The default *fontdict* is::
+
+               {'fontsize': rcParams['axes.titlesize'],
+                'fontweight': rcParams['axes.titleweight'],
+                'verticalalignment': 'baseline',
+                'horizontalalignment': loc}
+
+        minor : bool, optional
+            Whether to set the minor ticklabels rather than the major ones.
+
+        Returns
+        -------
+        A list of `~.text.Text` instances.
+
+        Other Parameters
+        ----------------
+        **kwargs : `~.text.Text` properties.
+        """
+        if fontdict is not None:
+            kwargs.update(fontdict)
+        return self.yaxis.set_ticklabels(labels,
+                                         minor=minor, **kwargs)
+
+    def xaxis_date(self, tz=None):
+        """
+        Sets up x-axis ticks and labels that treat the x data as dates.
+
+        Parameters
+        ----------
+        tz : str or `tzinfo` instance, optional
+            Timezone.  Defaults to :rc:`timezone`.
+        """
+        # should be enough to inform the unit conversion interface
+        # dates are coming in
+        self.xaxis.axis_date(tz)
+
+    def yaxis_date(self, tz=None):
+        """
+        Sets up y-axis ticks and labels that treat the y data as dates.
+
+        Parameters
+        ----------
+        tz : str or `tzinfo` instance, optional
+            Timezone.  Defaults to :rc:`timezone`.
+        """
+        self.yaxis.axis_date(tz)
+
+    def format_xdata(self, x):
+        """
+        Return *x* formatted as an x-value.
+
+        This function will use the `.fmt_xdata` attribute if it is not None,
+        else will fall back on the xaxis major formatter.
+        """
+        return (self.fmt_xdata if self.fmt_xdata is not None
+                else self.xaxis.get_major_formatter().format_data_short)(x)
+
+    def format_ydata(self, y):
+        """
+        Return *y* formatted as an y-value.
+
+        This function will use the `.fmt_ydata` attribute if it is not None,
+        else will fall back on the yaxis major formatter.
+        """
+        return (self.fmt_ydata if self.fmt_ydata is not None
+                else self.yaxis.get_major_formatter().format_data_short)(y)
+
+    def format_coord(self, x, y):
+        """Return a format string formatting the *x*, *y* coordinates."""
+        if x is None:
+            xs = '???'
+        else:
+            xs = self.format_xdata(x)
+        if y is None:
+            ys = '???'
+        else:
+            ys = self.format_ydata(y)
+        return 'x=%s y=%s' % (xs, ys)
+
+    def minorticks_on(self):
+        """
+        Display minor ticks on the axes.
+
+        Displaying minor ticks may reduce performance; you may turn them off
+        using `minorticks_off()` if drawing speed is a problem.
+        """
+        for ax in (self.xaxis, self.yaxis):
+            scale = ax.get_scale()
+            if scale == 'log':
+                s = ax._scale
+                ax.set_minor_locator(mticker.LogLocator(s.base, s.subs))
+            elif scale == 'symlog':
+                s = ax._scale
+                ax.set_minor_locator(
+                    mticker.SymmetricalLogLocator(s._transform, s.subs))
+            else:
+                ax.set_minor_locator(mticker.AutoMinorLocator())
+
+    def minorticks_off(self):
+        """Remove minor ticks from the axes."""
+        self.xaxis.set_minor_locator(mticker.NullLocator())
+        self.yaxis.set_minor_locator(mticker.NullLocator())
+
+    # Interactive manipulation
+
+    def can_zoom(self):
+        """
+        Return *True* if this axes supports the zoom box button functionality.
+        """
+        return True
+
+    def can_pan(self):
+        """
+        Return *True* if this axes supports any pan/zoom button functionality.
+        """
+        return True
+
+    def get_navigate(self):
+        """
+        Get whether the axes responds to navigation commands
+        """
+        return self._navigate
+
+    def set_navigate(self, b):
+        """
+        Set whether the axes responds to navigation toolbar commands
+
+        Parameters
+        ----------
+        b : bool
+        """
+        self._navigate = b
+
+    def get_navigate_mode(self):
+        """
+        Get the navigation toolbar button status: 'PAN', 'ZOOM', or None
+        """
+        return self._navigate_mode
+
+    def set_navigate_mode(self, b):
+        """
+        Set the navigation toolbar button status;
+
+        .. warning::
+            this is not a user-API function.
+
+        """
+        self._navigate_mode = b
+
+    def _get_view(self):
+        """
+        Save information required to reproduce the current view.
+
+        Called before a view is changed, such as during a pan or zoom
+        initiated by the user. You may return any information you deem
+        necessary to describe the view.
+
+        .. note::
+
+            Intended to be overridden by new projection types, but if not, the
+            default implementation saves the view limits. You *must* implement
+            :meth:`_set_view` if you implement this method.
+        """
+        xmin, xmax = self.get_xlim()
+        ymin, ymax = self.get_ylim()
+        return (xmin, xmax, ymin, ymax)
+
+    def _set_view(self, view):
+        """
+        Apply a previously saved view.
+
+        Called when restoring a view, such as with the navigation buttons.
+
+        .. note::
+
+            Intended to be overridden by new projection types, but if not, the
+            default implementation restores the view limits. You *must*
+            implement :meth:`_get_view` if you implement this method.
+        """
+        xmin, xmax, ymin, ymax = view
+        self.set_xlim((xmin, xmax))
+        self.set_ylim((ymin, ymax))
+
+    def _set_view_from_bbox(self, bbox, direction='in',
+                            mode=None, twinx=False, twiny=False):
+        """
+        Update view from a selection bbox.
+
+        .. note::
+
+            Intended to be overridden by new projection types, but if not, the
+            default implementation sets the view limits to the bbox directly.
+
+        Parameters
+        ----------
+        bbox : 4-tuple or 3 tuple
+            * If bbox is a 4 tuple, it is the selected bounding box limits,
+              in *display* coordinates.
+            * If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where
+              (xp, yp) is the center of zooming and scl the scale factor to
+              zoom by.
+
+        direction : str
+            The direction to apply the bounding box.
+                * `'in'` - The bounding box describes the view directly, i.e.,
+                           it zooms in.
+                * `'out'` - The bounding box describes the size to make the
+                            existing view, i.e., it zooms out.
+
+        mode : str or None
+            The selection mode, whether to apply the bounding box in only the
+            `'x'` direction, `'y'` direction or both (`None`).
+
+        twinx : bool
+            Whether this axis is twinned in the *x*-direction.
+
+        twiny : bool
+            Whether this axis is twinned in the *y*-direction.
+        """
+        Xmin, Xmax = self.get_xlim()
+        Ymin, Ymax = self.get_ylim()
+
+        if len(bbox) == 3:
+            # Zooming code
+            xp, yp, scl = bbox
+
+            # Should not happen
+            if scl == 0:
+                scl = 1.
+
+            # direction = 'in'
+            if scl > 1:
+                direction = 'in'
+            else:
+                direction = 'out'
+                scl = 1/scl
+
+            # get the limits of the axes
+            tranD2C = self.transData.transform
+            xmin, ymin = tranD2C((Xmin, Ymin))
+            xmax, ymax = tranD2C((Xmax, Ymax))
+
+            # set the range
+            xwidth = xmax - xmin
+            ywidth = ymax - ymin
+            xcen = (xmax + xmin)*.5
+            ycen = (ymax + ymin)*.5
+            xzc = (xp*(scl - 1) + xcen)/scl
+            yzc = (yp*(scl - 1) + ycen)/scl
+
+            bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl,
+                    xzc + xwidth/2./scl, yzc + ywidth/2./scl]
+        elif len(bbox) != 4:
+            # should be len 3 or 4 but nothing else
+            cbook._warn_external(
+                "Warning in _set_view_from_bbox: bounding box is not a tuple "
+                "of length 3 or 4. Ignoring the view change.")
+            return
+
+        # Just grab bounding box
+        lastx, lasty, x, y = bbox
+
+        # zoom to rect
+        inverse = self.transData.inverted()
+        (lastx, lasty), (x, y) = inverse.transform([(lastx, lasty), (x, y)])
+
+        if twinx:
+            x0, x1 = Xmin, Xmax
+        else:
+            if Xmin < Xmax:
+                if x < lastx:
+                    x0, x1 = x, lastx
+                else:
+                    x0, x1 = lastx, x
+                if x0 < Xmin:
+                    x0 = Xmin
+                if x1 > Xmax:
+                    x1 = Xmax
+            else:
+                if x > lastx:
+                    x0, x1 = x, lastx
+                else:
+                    x0, x1 = lastx, x
+                if x0 > Xmin:
+                    x0 = Xmin
+                if x1 < Xmax:
+                    x1 = Xmax
+
+        if twiny:
+            y0, y1 = Ymin, Ymax
+        else:
+            if Ymin < Ymax:
+                if y < lasty:
+                    y0, y1 = y, lasty
+                else:
+                    y0, y1 = lasty, y
+                if y0 < Ymin:
+                    y0 = Ymin
+                if y1 > Ymax:
+                    y1 = Ymax
+            else:
+                if y > lasty:
+                    y0, y1 = y, lasty
+                else:
+                    y0, y1 = lasty, y
+                if y0 > Ymin:
+                    y0 = Ymin
+                if y1 < Ymax:
+                    y1 = Ymax
+
+        if direction == 'in':
+            if mode == 'x':
+                self.set_xlim((x0, x1))
+            elif mode == 'y':
+                self.set_ylim((y0, y1))
+            else:
+                self.set_xlim((x0, x1))
+                self.set_ylim((y0, y1))
+        elif direction == 'out':
+            if self.get_xscale() == 'log':
+                alpha = np.log(Xmax / Xmin) / np.log(x1 / x0)
+                rx1 = pow(Xmin / x0, alpha) * Xmin
+                rx2 = pow(Xmax / x0, alpha) * Xmin
+            else:
+                alpha = (Xmax - Xmin) / (x1 - x0)
+                rx1 = alpha * (Xmin - x0) + Xmin
+                rx2 = alpha * (Xmax - x0) + Xmin
+            if self.get_yscale() == 'log':
+                alpha = np.log(Ymax / Ymin) / np.log(y1 / y0)
+                ry1 = pow(Ymin / y0, alpha) * Ymin
+                ry2 = pow(Ymax / y0, alpha) * Ymin
+            else:
+                alpha = (Ymax - Ymin) / (y1 - y0)
+                ry1 = alpha * (Ymin - y0) + Ymin
+                ry2 = alpha * (Ymax - y0) + Ymin
+
+            if mode == 'x':
+                self.set_xlim((rx1, rx2))
+            elif mode == 'y':
+                self.set_ylim((ry1, ry2))
+            else:
+                self.set_xlim((rx1, rx2))
+                self.set_ylim((ry1, ry2))
+
+    def start_pan(self, x, y, button):
+        """
+        Called when a pan operation has started.
+
+        *x*, *y* are the mouse coordinates in display coords.
+        button is the mouse button number:
+
+        * 1: LEFT
+        * 2: MIDDLE
+        * 3: RIGHT
+
+        .. note::
+
+            Intended to be overridden by new projection types.
+
+        """
+        self._pan_start = types.SimpleNamespace(
+            lim=self.viewLim.frozen(),
+            trans=self.transData.frozen(),
+            trans_inverse=self.transData.inverted().frozen(),
+            bbox=self.bbox.frozen(),
+            x=x,
+            y=y)
+
+    def end_pan(self):
+        """
+        Called when a pan operation completes (when the mouse button
+        is up.)
+
+        .. note::
+
+            Intended to be overridden by new projection types.
+
+        """
+        del self._pan_start
+
+    def drag_pan(self, button, key, x, y):
+        """
+        Called when the mouse moves during a pan operation.
+
+        *button* is the mouse button number:
+
+        * 1: LEFT
+        * 2: MIDDLE
+        * 3: RIGHT
+
+        *key* is a "shift" key
+
+        *x*, *y* are the mouse coordinates in display coords.
+
+        .. note::
+
+            Intended to be overridden by new projection types.
+
+        """
+        def format_deltas(key, dx, dy):
+            if key == 'control':
+                if abs(dx) > abs(dy):
+                    dy = dx
+                else:
+                    dx = dy
+            elif key == 'x':
+                dy = 0
+            elif key == 'y':
+                dx = 0
+            elif key == 'shift':
+                if 2 * abs(dx) < abs(dy):
+                    dx = 0
+                elif 2 * abs(dy) < abs(dx):
+                    dy = 0
+                elif abs(dx) > abs(dy):
+                    dy = dy / abs(dy) * abs(dx)
+                else:
+                    dx = dx / abs(dx) * abs(dy)
+            return dx, dy
+
+        p = self._pan_start
+        dx = x - p.x
+        dy = y - p.y
+        if dx == dy == 0:
+            return
+        if button == 1:
+            dx, dy = format_deltas(key, dx, dy)
+            result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse)
+        elif button == 3:
+            try:
+                dx = -dx / self.bbox.width
+                dy = -dy / self.bbox.height
+                dx, dy = format_deltas(key, dx, dy)
+                if self.get_aspect() != 'auto':
+                    dx = dy = 0.5 * (dx + dy)
+                alpha = np.power(10.0, (dx, dy))
+                start = np.array([p.x, p.y])
+                oldpoints = p.lim.transformed(p.trans)
+                newpoints = start + alpha * (oldpoints - start)
+                result = (mtransforms.Bbox(newpoints)
+                          .transformed(p.trans_inverse))
+            except OverflowError:
+                cbook._warn_external('Overflow while panning')
+                return
+        else:
+            return
+
+        valid = np.isfinite(result.transformed(p.trans))
+        points = result.get_points().astype(object)
+        # Just ignore invalid limits (typically, underflow in log-scale).
+        points[~valid] = None
+        self.set_xlim(points[:, 0])
+        self.set_ylim(points[:, 1])
+
+    def get_children(self):
+        # docstring inherited.
+        return [
+            *self.collections,
+            *self.patches,
+            *self.lines,
+            *self.texts,
+            *self.artists,
+            *self.spines.values(),
+            *self._get_axis_list(),
+            self.title, self._left_title, self._right_title,
+            *self.tables,
+            *self.images,
+            *self.child_axes,
+            *([self.legend_] if self.legend_ is not None else []),
+            self.patch,
+        ]
+
+    def contains(self, mouseevent):
+        # docstring inherited.
+        inside, info = self._default_contains(mouseevent)
+        if inside is not None:
+            return inside, info
+        return self.patch.contains(mouseevent)
+
+    def contains_point(self, point):
+        """
+        Return whether *point* (pair of pixel coordinates) is inside the axes
+        patch.
+        """
+        return self.patch.contains_point(point, radius=1.0)
+
+    def get_default_bbox_extra_artists(self):
+        """
+        Return a default list of artists that are used for the bounding box
+        calculation.
+
+        Artists are excluded either by not being visible or
+        ``artist.set_in_layout(False)``.
+        """
+
+        artists = self.get_children()
+
+        if not (self.axison and self._frameon):
+            # don't do bbox on spines if frame not on.
+            for spine in self.spines.values():
+                artists.remove(spine)
+
+        if not self.axison:
+            for _axis in self._get_axis_list():
+                artists.remove(_axis)
+
+        return [artist for artist in artists
+                if (artist.get_visible() and artist.get_in_layout())]
+
+    def get_tightbbox(self, renderer, call_axes_locator=True,
+                      bbox_extra_artists=None):
+        """
+        Return the tight bounding box of the axes, including axis and their
+        decorators (xlabel, title, etc).
+
+        Artists that have ``artist.set_in_layout(False)`` are not included
+        in the bbox.
+
+        Parameters
+        ----------
+        renderer : `.RendererBase` instance
+            renderer that will be used to draw the figures (i.e.
+            ``fig.canvas.get_renderer()``)
+
+        bbox_extra_artists : list of `.Artist` or ``None``
+            List of artists to include in the tight bounding box.  If
+            ``None`` (default), then all artist children of the axes are
+            included in the tight bounding box.
+
+        call_axes_locator : boolean (default ``True``)
+            If *call_axes_locator* is ``False``, it does not call the
+            ``_axes_locator`` attribute, which is necessary to get the correct
+            bounding box. ``call_axes_locator=False`` can be used if the
+            caller is only interested in the relative size of the tightbbox
+            compared to the axes bbox.
+
+        Returns
+        -------
+        bbox : `.BboxBase`
+            bounding box in figure pixel coordinates.
+
+        See Also
+        --------
+        matplotlib.axes.Axes.get_window_extent
+        matplotlib.axis.Axis.get_tightbbox
+        matplotlib.spines.Spine.get_window_extent
+
+        """
+
+        bb = []
+
+        if not self.get_visible():
+            return None
+
+        locator = self.get_axes_locator()
+        if locator and call_axes_locator:
+            pos = locator(self, renderer)
+            self.apply_aspect(pos)
+        else:
+            self.apply_aspect()
+
+        if self.axison:
+            bb_xaxis = self.xaxis.get_tightbbox(renderer)
+            if bb_xaxis:
+                bb.append(bb_xaxis)
+
+            bb_yaxis = self.yaxis.get_tightbbox(renderer)
+            if bb_yaxis:
+                bb.append(bb_yaxis)
+
+        self._update_title_position(renderer)
+        axbbox = self.get_window_extent(renderer)
+        bb.append(axbbox)
+
+        self._update_title_position(renderer)
+        if self.title.get_visible():
+            bb.append(self.title.get_window_extent(renderer))
+        if self._left_title.get_visible():
+            bb.append(self._left_title.get_window_extent(renderer))
+        if self._right_title.get_visible():
+            bb.append(self._right_title.get_window_extent(renderer))
+
+        bb.append(self.get_window_extent(renderer))
+
+        bbox_artists = bbox_extra_artists
+        if bbox_artists is None:
+            bbox_artists = self.get_default_bbox_extra_artists()
+
+        for a in bbox_artists:
+            # Extra check here to quickly see if clipping is on and
+            # contained in the axes.  If it is, don't get the tightbbox for
+            # this artist because this can be expensive:
+            clip_extent = a._get_clipping_extent_bbox()
+            if clip_extent is not None:
+                clip_extent = mtransforms.Bbox.intersection(clip_extent,
+                    axbbox)
+                if np.all(clip_extent.extents == axbbox.extents):
+                    # clip extent is inside the axes bbox so don't check
+                    # this artist
+                    continue
+            bbox = a.get_tightbbox(renderer)
+            if (bbox is not None
+                    and 0 < bbox.width < np.inf
+                    and 0 < bbox.height < np.inf):
+                bb.append(bbox)
+        _bbox = mtransforms.Bbox.union(
+            [b for b in bb if b.width != 0 or b.height != 0])
+
+        return _bbox
+
+    def _make_twin_axes(self, *args, **kwargs):
+        """Make a twinx axes of self. This is used for twinx and twiny."""
+        # Typically, SubplotBase._make_twin_axes is called instead of this.
+        if 'sharex' in kwargs and 'sharey' in kwargs:
+            raise ValueError("Twinned Axes may share only one axis")
+        ax2 = self.figure.add_axes(self.get_position(True), *args, **kwargs)
+        self.set_adjustable('datalim')
+        ax2.set_adjustable('datalim')
+        self._twinned_axes.join(self, ax2)
+        return ax2
+
+    def twinx(self):
+        """
+        Create a twin Axes sharing the xaxis.
+
+        Create a new Axes with an invisible x-axis and an independent
+        y-axis positioned opposite to the original one (i.e. at right). The
+        x-axis autoscale setting will be inherited from the original
+        Axes.  To ensure that the tick marks of both y-axes align, see
+        `~matplotlib.ticker.LinearLocator`.
+
+        Returns
+        -------
+        ax_twin : Axes
+            The newly created Axes instance
+
+        Notes
+        -----
+        For those who are 'picking' artists while using twinx, pick
+        events are only called for the artists in the top-most axes.
+        """
+        ax2 = self._make_twin_axes(sharex=self)
+        ax2.yaxis.tick_right()
+        ax2.yaxis.set_label_position('right')
+        ax2.yaxis.set_offset_position('right')
+        ax2.set_autoscalex_on(self.get_autoscalex_on())
+        self.yaxis.tick_left()
+        ax2.xaxis.set_visible(False)
+        ax2.patch.set_visible(False)
+        return ax2
+
+    def twiny(self):
+        """
+        Create a twin Axes sharing the yaxis.
+
+        Create a new Axes with an invisible y-axis and an independent
+        x-axis positioned opposite to the original one (i.e. at top). The
+        y-axis autoscale setting will be inherited from the original Axes.
+        To ensure that the tick marks of both x-axes align, see
+        `~matplotlib.ticker.LinearLocator`.
+
+        Returns
+        -------
+        ax_twin : Axes
+            The newly created Axes instance
+
+        Notes
+        -----
+        For those who are 'picking' artists while using twiny, pick
+        events are only called for the artists in the top-most axes.
+        """
+        ax2 = self._make_twin_axes(sharey=self)
+        ax2.xaxis.tick_top()
+        ax2.xaxis.set_label_position('top')
+        ax2.set_autoscaley_on(self.get_autoscaley_on())
+        self.xaxis.tick_bottom()
+        ax2.yaxis.set_visible(False)
+        ax2.patch.set_visible(False)
+        return ax2
+
+    def get_shared_x_axes(self):
+        """Return a reference to the shared axes Grouper object for x axes."""
+        return self._shared_x_axes
+
+    def get_shared_y_axes(self):
+        """Return a reference to the shared axes Grouper object for y axes."""
+        return self._shared_y_axes

+ 408 - 0
venv/lib/python3.8/site-packages/matplotlib/axes/_secondary_axes.py

@@ -0,0 +1,408 @@
+import numpy as np
+
+import matplotlib.cbook as cbook
+import matplotlib.docstring as docstring
+import matplotlib.ticker as mticker
+import matplotlib.transforms as mtransforms
+from matplotlib.axes._base import _AxesBase
+
+
+def _make_secondary_locator(rect, parent):
+    """
+    Helper function to locate the secondary axes.
+
+    A locator gets used in `Axes.set_aspect` to override the default
+    locations...  It is a function that takes an axes object and
+    a renderer and tells `set_aspect` where it is to be placed.
+
+    This locator make the transform be in axes-relative co-coordinates
+    because that is how we specify the "location" of the secondary axes.
+
+    Here *rect* is a rectangle [l, b, w, h] that specifies the
+    location for the axes in the transform given by *trans* on the
+    *parent*.
+    """
+    _rect = mtransforms.Bbox.from_bounds(*rect)
+    def secondary_locator(ax, renderer):
+        # delay evaluating transform until draw time because the
+        # parent transform may have changed (i.e. if window reesized)
+        bb = mtransforms.TransformedBbox(_rect, parent.transAxes)
+        tr = parent.figure.transFigure.inverted()
+        bb = mtransforms.TransformedBbox(bb, tr)
+        return bb
+
+    return secondary_locator
+
+
+class SecondaryAxis(_AxesBase):
+    """
+    General class to hold a Secondary_X/Yaxis.
+    """
+
+    def __init__(self, parent, orientation,
+                  location, functions, **kwargs):
+        """
+        See `.secondary_xaxis` and `.secondary_yaxis` for the doc string.
+        While there is no need for this to be private, it should really be
+        called by those higher level functions.
+        """
+
+        self._functions = functions
+        self._parent = parent
+        self._orientation = orientation
+        self._ticks_set = False
+
+        if self._orientation == 'x':
+            super().__init__(self._parent.figure, [0, 1., 1, 0.0001], **kwargs)
+            self._axis = self.xaxis
+            self._locstrings = ['top', 'bottom']
+            self._otherstrings = ['left', 'right']
+        elif self._orientation == 'y':
+            super().__init__(self._parent.figure, [0, 1., 0.0001, 1], **kwargs)
+            self._axis = self.yaxis
+            self._locstrings = ['right', 'left']
+            self._otherstrings = ['top', 'bottom']
+        self._parentscale = self._axis.get_scale()
+        # this gets positioned w/o constrained_layout so exclude:
+        self._layoutbox = None
+        self._poslayoutbox = None
+
+        self.set_location(location)
+        self.set_functions(functions)
+
+        # styling:
+        if self._orientation == 'x':
+            otheraxis = self.yaxis
+        else:
+            otheraxis = self.xaxis
+
+        otheraxis.set_major_locator(mticker.NullLocator())
+        otheraxis.set_ticks_position('none')
+
+        for st in self._otherstrings:
+            self.spines[st].set_visible(False)
+        for st in self._locstrings:
+            self.spines[st].set_visible(True)
+
+        if self._pos < 0.5:
+            # flip the location strings...
+            self._locstrings = self._locstrings[::-1]
+        self.set_alignment(self._locstrings[0])
+
+    def set_alignment(self, align):
+        """
+        Set if axes spine and labels are drawn at top or bottom (or left/right)
+        of the axes.
+
+        Parameters
+        ----------
+        align : str
+            either 'top' or 'bottom' for orientation='x' or
+            'left' or 'right' for orientation='y' axis.
+        """
+        if align in self._locstrings:
+            if align == self._locstrings[1]:
+                # need to change the orientation.
+                self._locstrings = self._locstrings[::-1]
+            elif align != self._locstrings[0]:
+                raise ValueError('"{}" is not a valid axis orientation, '
+                                 'not changing the orientation;'
+                                 'choose "{}" or "{}""'.format(align,
+                                 self._locstrings[0], self._locstrings[1]))
+            self.spines[self._locstrings[0]].set_visible(True)
+            self.spines[self._locstrings[1]].set_visible(False)
+            self._axis.set_ticks_position(align)
+            self._axis.set_label_position(align)
+
+    def set_location(self, location):
+        """
+        Set the vertical or horizontal location of the axes in
+        parent-normalized co-ordinates.
+
+        Parameters
+        ----------
+        location : {'top', 'bottom', 'left', 'right'} or float
+            The position to put the secondary axis.  Strings can be 'top' or
+            'bottom' for orientation='x' and 'right' or 'left' for
+            orientation='y'. A float indicates the relative position on the
+            parent axes to put the new axes, 0.0 being the bottom (or left)
+            and 1.0 being the top (or right).
+        """
+
+        # This puts the rectangle into figure-relative coordinates.
+        if isinstance(location, str):
+            if location in ['top', 'right']:
+                self._pos = 1.
+            elif location in ['bottom', 'left']:
+                self._pos = 0.
+            else:
+                raise ValueError("location must be '{}', '{}', or a "
+                                 "float, not '{}'".format(location,
+                                 self._locstrings[0], self._locstrings[1]))
+        else:
+            self._pos = location
+        self._loc = location
+
+        if self._orientation == 'x':
+            bounds = [0, self._pos, 1., 1e-10]
+        else:
+            bounds = [self._pos, 0, 1e-10, 1]
+
+        secondary_locator = _make_secondary_locator(bounds, self._parent)
+
+        # this locator lets the axes move in the parent axes coordinates.
+        # so it never needs to know where the parent is explicitly in
+        # figure co-ordinates.
+        # it gets called in `ax.apply_aspect() (of all places)
+        self.set_axes_locator(secondary_locator)
+
+    def apply_aspect(self, position=None):
+        # docstring inherited.
+        self._set_lims()
+        super().apply_aspect(position)
+
+    @cbook._make_keyword_only("3.2", "minor")
+    def set_ticks(self, ticks, minor=False):
+        """
+        Set the x ticks with list of *ticks*
+
+        Parameters
+        ----------
+        ticks : list
+            List of x-axis tick locations.
+        minor : bool, optional
+            If ``False`` sets major ticks, if ``True`` sets minor ticks.
+            Default is ``False``.
+        """
+        ret = self._axis.set_ticks(ticks, minor=minor)
+        self.stale = True
+        self._ticks_set = True
+        return ret
+
+    def set_functions(self, functions):
+        """
+        Set how the secondary axis converts limits from the parent axes.
+
+        Parameters
+        ----------
+        functions : 2-tuple of func, or `Transform` with an inverse.
+            Transform between the parent axis values and the secondary axis
+            values.
+
+            If supplied as a 2-tuple of functions, the first function is
+            the forward transform function and the second is the inverse
+            transform.
+
+            If a transform is supplied, then the transform must have an
+            inverse.
+        """
+
+        if self._orientation == 'x':
+            set_scale = self.set_xscale
+            parent_scale = self._parent.get_xscale()
+        else:
+            set_scale = self.set_yscale
+            parent_scale = self._parent.get_yscale()
+        # we need to use a modified scale so the scale can receive the
+        # transform.  Only types supported are linear and log10 for now.
+        # Probably possible to add other transforms as a todo...
+        if parent_scale == 'log':
+            defscale = 'functionlog'
+        else:
+            defscale = 'function'
+
+        if (isinstance(functions, tuple) and len(functions) == 2 and
+            callable(functions[0]) and callable(functions[1])):
+            # make an arbitrary convert from a two-tuple of functions
+            # forward and inverse.
+            self._functions = functions
+        elif functions is None:
+            self._functions = (lambda x: x, lambda x: x)
+        else:
+            raise ValueError('functions argument of secondary axes '
+                             'must be a two-tuple of callable functions '
+                             'with the first function being the transform '
+                             'and the second being the inverse')
+        # need to invert the roles here for the ticks to line up.
+        set_scale(defscale, functions=self._functions[::-1])
+
+    def draw(self, renderer=None, inframe=False):
+        """
+        Draw the secondary axes.
+
+        Consults the parent axes for its limits and converts them
+        using the converter specified by
+        `~.axes._secondary_axes.set_functions` (or *functions*
+        parameter when axes initialized.)
+        """
+        self._set_lims()
+        # this sets the scale in case the parent has set its scale.
+        self._set_scale()
+        super().draw(renderer=renderer, inframe=inframe)
+
+    def _set_scale(self):
+        """
+        Check if parent has set its scale
+        """
+
+        if self._orientation == 'x':
+            pscale = self._parent.xaxis.get_scale()
+            set_scale = self.set_xscale
+        if self._orientation == 'y':
+            pscale = self._parent.yaxis.get_scale()
+            set_scale = self.set_yscale
+        if pscale == self._parentscale:
+            return
+        else:
+            self._parentscale = pscale
+
+        if pscale == 'log':
+            defscale = 'functionlog'
+        else:
+            defscale = 'function'
+
+        if self._ticks_set:
+            ticks = self._axis.get_ticklocs()
+
+        # need to invert the roles here for the ticks to line up.
+        set_scale(defscale, functions=self._functions[::-1])
+
+        # OK, set_scale sets the locators, but if we've called
+        # axsecond.set_ticks, we want to keep those.
+        if self._ticks_set:
+            self._axis.set_major_locator(mticker.FixedLocator(ticks))
+
+    def _set_lims(self):
+        """
+        Set the limits based on parent limits and the convert method
+        between the parent and this secondary axes.
+        """
+        if self._orientation == 'x':
+            lims = self._parent.get_xlim()
+            set_lim = self.set_xlim
+        if self._orientation == 'y':
+            lims = self._parent.get_ylim()
+            set_lim = self.set_ylim
+        order = lims[0] < lims[1]
+        lims = self._functions[0](np.array(lims))
+        neworder = lims[0] < lims[1]
+        if neworder != order:
+            # Flip because the transform will take care of the flipping.
+            lims = lims[::-1]
+        set_lim(lims)
+
+    def set_aspect(self, *args, **kwargs):
+        """
+        Secondary axes cannot set the aspect ratio, so calling this just
+        sets a warning.
+        """
+        cbook._warn_external("Secondary axes can't set the aspect ratio")
+
+    def set_xlabel(self, xlabel, fontdict=None, labelpad=None, **kwargs):
+        """
+        Set the label for the x-axis.
+
+        Parameters
+        ----------
+        xlabel : str
+            The label text.
+
+        labelpad : scalar, optional, default: None
+            Spacing in points between the label and the x-axis.
+
+        Other Parameters
+        ----------------
+        **kwargs : `.Text` properties
+            `.Text` properties control the appearance of the label.
+
+        See also
+        --------
+        text : for information on how override and the optional args work
+        """
+        if labelpad is not None:
+            self.xaxis.labelpad = labelpad
+        return self.xaxis.set_label_text(xlabel, fontdict, **kwargs)
+
+    def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs):
+        """
+        Set the label for the x-axis.
+
+        Parameters
+        ----------
+        ylabel : str
+            The label text.
+
+        labelpad : scalar, optional, default: None
+            Spacing in points between the label and the x-axis.
+
+        Other Parameters
+        ----------------
+        **kwargs : `.Text` properties
+            `.Text` properties control the appearance of the label.
+
+        See also
+        --------
+        text : for information on how override and the optional args work
+        """
+        if labelpad is not None:
+            self.yaxis.labelpad = labelpad
+        return self.yaxis.set_label_text(ylabel, fontdict, **kwargs)
+
+    def set_color(self, color):
+        """
+        Change the color of the secondary axes and all decorators.
+
+        Parameters
+        ----------
+        color : Matplotlib color
+        """
+        if self._orientation == 'x':
+            self.tick_params(axis='x', colors=color)
+            self.spines['bottom'].set_color(color)
+            self.spines['top'].set_color(color)
+            self.xaxis.label.set_color(color)
+        else:
+            self.tick_params(axis='y', colors=color)
+            self.spines['left'].set_color(color)
+            self.spines['right'].set_color(color)
+            self.yaxis.label.set_color(color)
+
+
+_secax_docstring = '''
+Warnings
+--------
+This method is experimental as of 3.1, and the API may change.
+
+Parameters
+----------
+location : {'top', 'bottom', 'left', 'right'} or float
+    The position to put the secondary axis.  Strings can be 'top' or
+    'bottom' for orientation='x' and 'right' or 'left' for
+    orientation='y'. A float indicates the relative position on the
+    parent axes to put the new axes, 0.0 being the bottom (or left)
+    and 1.0 being the top (or right).
+
+functions : 2-tuple of func, or Transform with an inverse
+
+    If a 2-tuple of functions, the user specifies the transform
+    function and its inverse.  i.e.
+    `functions=(lambda x: 2 / x, lambda x: 2 / x)` would be an
+    reciprocal transform with a factor of 2.
+
+    The user can also directly supply a subclass of
+    `.transforms.Transform` so long as it has an inverse.
+
+    See :doc:`/gallery/subplots_axes_and_figures/secondary_axis`
+    for examples of making these conversions.
+
+
+Other Parameters
+----------------
+**kwargs : `~matplotlib.axes.Axes` properties.
+    Other miscellaneous axes parameters.
+
+Returns
+-------
+ax : axes._secondary_axes.SecondaryAxis
+'''
+docstring.interpd.update(_secax_docstring=_secax_docstring)

+ 255 - 0
venv/lib/python3.8/site-packages/matplotlib/axes/_subplots.py

@@ -0,0 +1,255 @@
+import functools
+import uuid
+
+from matplotlib import cbook, docstring
+import matplotlib.artist as martist
+from matplotlib.axes._axes import Axes
+from matplotlib.gridspec import GridSpec, SubplotSpec
+import matplotlib._layoutbox as layoutbox
+
+
+class SubplotBase:
+    """
+    Base class for subplots, which are :class:`Axes` instances with
+    additional methods to facilitate generating and manipulating a set
+    of :class:`Axes` within a figure.
+    """
+
+    def __init__(self, fig, *args, **kwargs):
+        """
+        Parameters
+        ----------
+        fig : `matplotlib.figure.Figure`
+
+        *args : tuple (*nrows*, *ncols*, *index*) or int
+            The array of subplots in the figure has dimensions ``(nrows,
+            ncols)``, and *index* is the index of the subplot being created.
+            *index* starts at 1 in the upper left corner and increases to the
+            right.
+
+            If *nrows*, *ncols*, and *index* are all single digit numbers, then
+            *args* can be passed as a single 3-digit number (e.g. 234 for
+            (2, 3, 4)).
+        """
+
+        self.figure = fig
+
+        if len(args) == 1:
+            if isinstance(args[0], SubplotSpec):
+                self._subplotspec = args[0]
+            else:
+                try:
+                    s = str(int(args[0]))
+                    rows, cols, num = map(int, s)
+                except ValueError:
+                    raise ValueError('Single argument to subplot must be '
+                        'a 3-digit integer')
+                self._subplotspec = GridSpec(rows, cols,
+                                             figure=self.figure)[num - 1]
+                # num - 1 for converting from MATLAB to python indexing
+        elif len(args) == 3:
+            rows, cols, num = args
+            rows = int(rows)
+            cols = int(cols)
+            if rows <= 0:
+                raise ValueError(f'Number of rows must be > 0, not {rows}')
+            if cols <= 0:
+                raise ValueError(f'Number of columns must be > 0, not {cols}')
+            if isinstance(num, tuple) and len(num) == 2:
+                num = [int(n) for n in num]
+                self._subplotspec = GridSpec(
+                        rows, cols,
+                        figure=self.figure)[(num[0] - 1):num[1]]
+            else:
+                if num < 1 or num > rows*cols:
+                    raise ValueError(
+                        f"num must be 1 <= num <= {rows*cols}, not {num}")
+                self._subplotspec = GridSpec(
+                        rows, cols, figure=self.figure)[int(num) - 1]
+                # num - 1 for converting from MATLAB to python indexing
+        else:
+            raise ValueError(f'Illegal argument(s) to subplot: {args}')
+
+        self.update_params()
+
+        # _axes_class is set in the subplot_class_factory
+        self._axes_class.__init__(self, fig, self.figbox, **kwargs)
+        # add a layout box to this, for both the full axis, and the poss
+        # of the axis.  We need both because the axes may become smaller
+        # due to parasitic axes and hence no longer fill the subplotspec.
+        if self._subplotspec._layoutbox is None:
+            self._layoutbox = None
+            self._poslayoutbox = None
+        else:
+            name = self._subplotspec._layoutbox.name + '.ax'
+            name = name + layoutbox.seq_id()
+            self._layoutbox = layoutbox.LayoutBox(
+                    parent=self._subplotspec._layoutbox,
+                    name=name,
+                    artist=self)
+            self._poslayoutbox = layoutbox.LayoutBox(
+                    parent=self._layoutbox,
+                    name=self._layoutbox.name+'.pos',
+                    pos=True, subplot=True, artist=self)
+
+    def __reduce__(self):
+        # get the first axes class which does not inherit from a subplotbase
+        axes_class = next(
+            c for c in type(self).__mro__
+            if issubclass(c, Axes) and not issubclass(c, SubplotBase))
+        return (_picklable_subplot_class_constructor,
+                (axes_class,),
+                self.__getstate__())
+
+    def get_geometry(self):
+        """Get the subplot geometry, e.g., (2, 2, 3)."""
+        rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
+        return rows, cols, num1 + 1  # for compatibility
+
+    # COVERAGE NOTE: Never used internally or from examples
+    def change_geometry(self, numrows, numcols, num):
+        """Change subplot geometry, e.g., from (1, 1, 1) to (2, 2, 3)."""
+        self._subplotspec = GridSpec(numrows, numcols,
+                                     figure=self.figure)[num - 1]
+        self.update_params()
+        self.set_position(self.figbox)
+
+    def get_subplotspec(self):
+        """get the SubplotSpec instance associated with the subplot"""
+        return self._subplotspec
+
+    def set_subplotspec(self, subplotspec):
+        """set the SubplotSpec instance associated with the subplot"""
+        self._subplotspec = subplotspec
+
+    def get_gridspec(self):
+        """get the GridSpec instance associated with the subplot"""
+        return self._subplotspec.get_gridspec()
+
+    def update_params(self):
+        """update the subplot position from fig.subplotpars"""
+        self.figbox, _, _, self.numRows, self.numCols = \
+            self.get_subplotspec().get_position(self.figure,
+                                                return_all=True)
+
+    @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start")
+    @property
+    def rowNum(self):
+        return self.get_subplotspec().rowspan.start
+
+    @cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start")
+    @property
+    def colNum(self):
+        return self.get_subplotspec().colspan.start
+
+    def is_first_row(self):
+        return self.get_subplotspec().rowspan.start == 0
+
+    def is_last_row(self):
+        return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows
+
+    def is_first_col(self):
+        return self.get_subplotspec().colspan.start == 0
+
+    def is_last_col(self):
+        return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols
+
+    def label_outer(self):
+        """
+        Only show "outer" labels and tick labels.
+
+        x-labels are only kept for subplots on the last row; y-labels only for
+        subplots on the first column.
+        """
+        lastrow = self.is_last_row()
+        firstcol = self.is_first_col()
+        if not lastrow:
+            for label in self.get_xticklabels(which="both"):
+                label.set_visible(False)
+            self.get_xaxis().get_offset_text().set_visible(False)
+            self.set_xlabel("")
+        if not firstcol:
+            for label in self.get_yticklabels(which="both"):
+                label.set_visible(False)
+            self.get_yaxis().get_offset_text().set_visible(False)
+            self.set_ylabel("")
+
+    def _make_twin_axes(self, *args, **kwargs):
+        """Make a twinx axes of self. This is used for twinx and twiny."""
+        if 'sharex' in kwargs and 'sharey' in kwargs:
+            # The following line is added in v2.2 to avoid breaking Seaborn,
+            # which currently uses this internal API.
+            if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
+                raise ValueError("Twinned Axes may share only one axis")
+        # The dance here with label is to force add_subplot() to create a new
+        # Axes (by passing in a label never seen before).  Note that this does
+        # not affect plot reactivation by subplot() as twin axes can never be
+        # reactivated by subplot().
+        sentinel = str(uuid.uuid4())
+        real_label = kwargs.pop("label", sentinel)
+        twin = self.figure.add_subplot(
+            self.get_subplotspec(), *args, label=sentinel, **kwargs)
+        if real_label is not sentinel:
+            twin.set_label(real_label)
+        self.set_adjustable('datalim')
+        twin.set_adjustable('datalim')
+        if self._layoutbox is not None and twin._layoutbox is not None:
+            # make the layout boxes be explicitly the same
+            twin._layoutbox.constrain_same(self._layoutbox)
+            twin._poslayoutbox.constrain_same(self._poslayoutbox)
+        self._twinned_axes.join(self, twin)
+        return twin
+
+
+# this here to support cartopy which was using a private part of the
+# API to register their Axes subclasses.
+
+# In 3.1 this should be changed to a dict subclass that warns on use
+# In 3.3 to a dict subclass that raises a useful exception on use
+# In 3.4 should be removed
+
+# The slow timeline is to give cartopy enough time to get several
+# release out before we break them.
+_subplot_classes = {}
+
+
+@functools.lru_cache(None)
+def subplot_class_factory(axes_class=None):
+    """
+    This makes a new class that inherits from `.SubplotBase` and the
+    given axes_class (which is assumed to be a subclass of `.axes.Axes`).
+    This is perhaps a little bit roundabout to make a new class on
+    the fly like this, but it means that a new Subplot class does
+    not have to be created for every type of Axes.
+    """
+    if axes_class is None:
+        axes_class = Axes
+    try:
+        # Avoid creating two different instances of GeoAxesSubplot...
+        # Only a temporary backcompat fix.  This should be removed in
+        # 3.4
+        return next(cls for cls in SubplotBase.__subclasses__()
+                    if cls.__bases__ == (SubplotBase, axes_class))
+    except StopIteration:
+        return type("%sSubplot" % axes_class.__name__,
+                    (SubplotBase, axes_class),
+                    {'_axes_class': axes_class})
+
+
+# This is provided for backward compatibility
+Subplot = subplot_class_factory()
+
+
+def _picklable_subplot_class_constructor(axes_class):
+    """
+    This stub class exists to return the appropriate subplot class when called
+    with an axes class. This is purely to allow pickling of Axes and Subplots.
+    """
+    subplot_class = subplot_class_factory(axes_class)
+    return subplot_class.__new__(subplot_class)
+
+
+docstring.interpd.update(Axes=martist.kwdoc(Axes))
+docstring.dedent_interpd(Axes.__init__)
+
+docstring.interpd.update(Subplot=martist.kwdoc(Axes))

+ 2496 - 0
venv/lib/python3.8/site-packages/matplotlib/axis.py

@@ -0,0 +1,2496 @@
+"""
+Classes for the ticks and x and y axis.
+"""
+
+import datetime
+import logging
+
+import numpy as np
+
+from matplotlib import rcParams
+import matplotlib.artist as martist
+import matplotlib.cbook as cbook
+import matplotlib.font_manager as font_manager
+import matplotlib.lines as mlines
+import matplotlib.scale as mscale
+import matplotlib.text as mtext
+import matplotlib.ticker as mticker
+import matplotlib.transforms as mtransforms
+import matplotlib.units as munits
+
+_log = logging.getLogger(__name__)
+
+GRIDLINE_INTERPOLATION_STEPS = 180
+
+# This list is being used for compatibility with Axes.grid, which
+# allows all Line2D kwargs.
+_line_AI = martist.ArtistInspector(mlines.Line2D)
+_line_param_names = _line_AI.get_setters()
+_line_param_aliases = [list(d)[0] for d in _line_AI.aliasd.values()]
+_gridline_param_names = ['grid_' + name
+                         for name in _line_param_names + _line_param_aliases]
+
+
+class Tick(martist.Artist):
+    """
+    Abstract base class for the axis ticks, grid lines and labels.
+
+    Ticks mark a position on an Axis. They contain two lines as markers and
+    two labels; one each for the bottom and top positions (in case of an
+    `.XAxis`) or for the left and right positions (in case of a `.YAxis`).
+
+    Attributes
+    ----------
+    tick1line : `.Line2D`
+        The left/bottom tick marker.
+    tick2line : `.Line2D`
+        The right/top tick marker.
+    gridline : `.Line2D`
+        The grid line associated with the label position.
+    label1 : `.Text`
+        The left/bottom tick label.
+    label2 : `.Text`
+        The right/top tick label.
+
+    """
+    def __init__(self, axes, loc, label,
+                 size=None,  # points
+                 width=None,
+                 color=None,
+                 tickdir=None,
+                 pad=None,
+                 labelsize=None,
+                 labelcolor=None,
+                 zorder=None,
+                 gridOn=None,  # defaults to axes.grid depending on
+                               # axes.grid.which
+                 tick1On=True,
+                 tick2On=True,
+                 label1On=True,
+                 label2On=False,
+                 major=True,
+                 labelrotation=0,
+                 grid_color=None,
+                 grid_linestyle=None,
+                 grid_linewidth=None,
+                 grid_alpha=None,
+                 **kw  # Other Line2D kwargs applied to gridlines.
+                 ):
+        """
+        bbox is the Bound2D bounding box in display coords of the Axes
+        loc is the tick location in data coords
+        size is the tick size in points
+        """
+        martist.Artist.__init__(self)
+
+        if gridOn is None:
+            if major and (rcParams['axes.grid.which'] in ('both', 'major')):
+                gridOn = rcParams['axes.grid']
+            elif (not major) and (rcParams['axes.grid.which']
+                                  in ('both', 'minor')):
+                gridOn = rcParams['axes.grid']
+            else:
+                gridOn = False
+
+        self.set_figure(axes.figure)
+        self.axes = axes
+
+        name = self.__name__.lower()
+
+        self._loc = loc
+
+        if size is None:
+            if major:
+                size = rcParams['%s.major.size' % name]
+            else:
+                size = rcParams['%s.minor.size' % name]
+        self._size = size
+
+        if width is None:
+            if major:
+                width = rcParams['%s.major.width' % name]
+            else:
+                width = rcParams['%s.minor.width' % name]
+        self._width = width
+
+        if color is None:
+            color = rcParams['%s.color' % name]
+        self._color = color
+
+        if pad is None:
+            if major:
+                pad = rcParams['%s.major.pad' % name]
+            else:
+                pad = rcParams['%s.minor.pad' % name]
+        self._base_pad = pad
+
+        if labelcolor is None:
+            labelcolor = rcParams['%s.color' % name]
+        self._labelcolor = labelcolor
+
+        if labelsize is None:
+            labelsize = rcParams['%s.labelsize' % name]
+        self._labelsize = labelsize
+
+        self._set_labelrotation(labelrotation)
+
+        if zorder is None:
+            if major:
+                zorder = mlines.Line2D.zorder + 0.01
+            else:
+                zorder = mlines.Line2D.zorder
+        self._zorder = zorder
+
+        self._grid_color = (rcParams['grid.color']
+                            if grid_color is None else grid_color)
+        self._grid_linestyle = (rcParams['grid.linestyle']
+                                if grid_linestyle is None else grid_linestyle)
+        self._grid_linewidth = (rcParams['grid.linewidth']
+                                if grid_linewidth is None else grid_linewidth)
+        self._grid_alpha = (rcParams['grid.alpha']
+                            if grid_alpha is None else grid_alpha)
+
+        self._grid_kw = {k[5:]: v for k, v in kw.items()}
+
+        self.apply_tickdir(tickdir)
+
+        self.tick1line = self._get_tick1line()
+        self.tick2line = self._get_tick2line()
+        self.gridline = self._get_gridline()
+        self.label1 = self._get_text1()
+        self.label2 = self._get_text2()
+
+        self.gridline.set_visible(gridOn)
+        self.tick1line.set_visible(tick1On)
+        self.tick2line.set_visible(tick2On)
+        self.label1.set_visible(label1On)
+        self.label2.set_visible(label2On)
+
+        self.update_position(loc)
+
+    for _old_name, _new_name in [
+            ("gridOn", "gridline"),
+            ("tick1On", "tick1line"),
+            ("tick2On", "tick2line"),
+            ("label1On", "label1"),
+            ("label2On", "label2")]:
+        locals()[_old_name] = property(
+            cbook.deprecated(
+                "3.1",
+                name=_old_name,
+                alternative="Tick.{}.get_visible".format(_new_name))(
+                    lambda self, _new_name=_new_name:
+                        getattr(self, _new_name).get_visible()),
+            cbook.deprecated(
+                "3.1",
+                name=_old_name,
+                alternative="Tick.{}.set_visible".format(_new_name))(
+                    lambda self, value, _new_name=_new_name:
+                        getattr(self, _new_name).set_visible(value)))
+    del _old_name, _new_name
+
+    @property
+    @cbook.deprecated("3.1", alternative="Tick.label1", pending=True)
+    def label(self):
+        return self.label1
+
+    def _set_labelrotation(self, labelrotation):
+        if isinstance(labelrotation, str):
+            mode = labelrotation
+            angle = 0
+        elif isinstance(labelrotation, (tuple, list)):
+            mode, angle = labelrotation
+        else:
+            mode = 'default'
+            angle = labelrotation
+        cbook._check_in_list(['auto', 'default'], labelrotation=mode)
+        self._labelrotation = (mode, angle)
+
+    def apply_tickdir(self, tickdir):
+        """Calculate self._pad and self._tickmarkers."""
+
+    def get_tickdir(self):
+        return self._tickdir
+
+    def get_tick_padding(self):
+        """Get the length of the tick outside of the axes."""
+        padding = {
+            'in': 0.0,
+            'inout': 0.5,
+            'out': 1.0
+        }
+        return self._size * padding[self._tickdir]
+
+    def get_children(self):
+        children = [self.tick1line, self.tick2line,
+                    self.gridline, self.label1, self.label2]
+        return children
+
+    def set_clip_path(self, clippath, transform=None):
+        # docstring inherited
+        martist.Artist.set_clip_path(self, clippath, transform)
+        self.gridline.set_clip_path(clippath, transform)
+        self.stale = True
+
+    def get_pad_pixels(self):
+        return self.figure.dpi * self._base_pad / 72
+
+    def contains(self, mouseevent):
+        """
+        Test whether the mouse event occurred in the Tick marks.
+
+        This function always returns false.  It is more useful to test if the
+        axis as a whole contains the mouse rather than the set of tick marks.
+        """
+        inside, info = self._default_contains(mouseevent)
+        if inside is not None:
+            return inside, info
+        return False, {}
+
+    def set_pad(self, val):
+        """
+        Set the tick label pad in points
+
+        Parameters
+        ----------
+        val : float
+        """
+        self._apply_params(pad=val)
+        self.stale = True
+
+    def get_pad(self):
+        'Get the value of the tick label pad in points'
+        return self._base_pad
+
+    def _get_text1(self):
+        'Get the default Text 1 instance'
+        pass
+
+    def _get_text2(self):
+        'Get the default Text 2 instance'
+        pass
+
+    def _get_tick1line(self):
+        'Get the default line2D instance for tick1'
+        pass
+
+    def _get_tick2line(self):
+        'Get the default line2D instance for tick2'
+        pass
+
+    def _get_gridline(self):
+        'Get the default grid Line2d instance for this tick'
+        pass
+
+    def get_loc(self):
+        'Return the tick location (data coords) as a scalar'
+        return self._loc
+
+    @martist.allow_rasterization
+    def draw(self, renderer):
+        if not self.get_visible():
+            self.stale = False
+            return
+        renderer.open_group(self.__name__, gid=self.get_gid())
+        for artist in [self.gridline, self.tick1line, self.tick2line,
+                       self.label1, self.label2]:
+            artist.draw(renderer)
+        renderer.close_group(self.__name__)
+        self.stale = False
+
+    def set_label1(self, s):
+        """
+        Set the label1 text.
+
+        Parameters
+        ----------
+        s : str
+        """
+        self.label1.set_text(s)
+        self.stale = True
+
+    set_label = set_label1
+
+    def set_label2(self, s):
+        """
+        Set the label2 text.
+
+        Parameters
+        ----------
+        s : str
+        """
+        self.label2.set_text(s)
+        self.stale = True
+
+    def _set_artist_props(self, a):
+        a.set_figure(self.figure)
+
+    def get_view_interval(self):
+        """
+        Return the view limits ``(min, max)`` of the axis the tick belongs to.
+        """
+        raise NotImplementedError('Derived must override')
+
+    def _apply_params(self, **kw):
+        for name, target in [("gridOn", self.gridline),
+                               ("tick1On", self.tick1line),
+                               ("tick2On", self.tick2line),
+                               ("label1On", self.label1),
+                               ("label2On", self.label2)]:
+            if name in kw:
+                target.set_visible(kw.pop(name))
+        if any(k in kw for k in ['size', 'width', 'pad', 'tickdir']):
+            self._size = kw.pop('size', self._size)
+            # Width could be handled outside this block, but it is
+            # convenient to leave it here.
+            self._width = kw.pop('width', self._width)
+            self._base_pad = kw.pop('pad', self._base_pad)
+            # apply_tickdir uses _size and _base_pad to make _pad,
+            # and also makes _tickmarkers.
+            self.apply_tickdir(kw.pop('tickdir', self._tickdir))
+            self.tick1line.set_marker(self._tickmarkers[0])
+            self.tick2line.set_marker(self._tickmarkers[1])
+            for line in (self.tick1line, self.tick2line):
+                line.set_markersize(self._size)
+                line.set_markeredgewidth(self._width)
+            # _get_text1_transform uses _pad from apply_tickdir.
+            trans = self._get_text1_transform()[0]
+            self.label1.set_transform(trans)
+            trans = self._get_text2_transform()[0]
+            self.label2.set_transform(trans)
+        tick_kw = {k: v for k, v in kw.items() if k in ['color', 'zorder']}
+        self.tick1line.set(**tick_kw)
+        self.tick2line.set(**tick_kw)
+        for k, v in tick_kw.items():
+            setattr(self, '_' + k, v)
+
+        if 'labelrotation' in kw:
+            self._set_labelrotation(kw.pop('labelrotation'))
+            self.label1.set(rotation=self._labelrotation[1])
+            self.label2.set(rotation=self._labelrotation[1])
+
+        label_kw = {k[5:]: v for k, v in kw.items()
+                    if k in ['labelsize', 'labelcolor']}
+        self.label1.set(**label_kw)
+        self.label2.set(**label_kw)
+        for k, v in label_kw.items():
+            # for labelsize the text objects covert str ('small')
+            # -> points. grab the integer from the `Text` object
+            # instead of saving the string representation
+            v = getattr(self.label1, 'get_' + k)()
+            setattr(self, '_label' + k, v)
+
+        grid_kw = {k[5:]: v for k, v in kw.items()
+                   if k in _gridline_param_names}
+        self.gridline.set(**grid_kw)
+        for k, v in grid_kw.items():
+            setattr(self, '_grid_' + k, v)
+
+    def update_position(self, loc):
+        'Set the location of tick in data coords with scalar *loc*'
+        raise NotImplementedError('Derived must override')
+
+    def _get_text1_transform(self):
+        raise NotImplementedError('Derived must override')
+
+    def _get_text2_transform(self):
+        raise NotImplementedError('Derived must override')
+
+
+class XTick(Tick):
+    """
+    Contains all the Artists needed to make an x tick - the tick line,
+    the label text and the grid line
+    """
+    __name__ = 'xtick'
+
+    def _get_text1_transform(self):
+        return self.axes.get_xaxis_text1_transform(self._pad)
+
+    def _get_text2_transform(self):
+        return self.axes.get_xaxis_text2_transform(self._pad)
+
+    def apply_tickdir(self, tickdir):
+        if tickdir is None:
+            tickdir = rcParams['%s.direction' % self.__name__.lower()]
+        self._tickdir = tickdir
+
+        if self._tickdir == 'in':
+            self._tickmarkers = (mlines.TICKUP, mlines.TICKDOWN)
+        elif self._tickdir == 'inout':
+            self._tickmarkers = ('|', '|')
+        else:
+            self._tickmarkers = (mlines.TICKDOWN, mlines.TICKUP)
+        self._pad = self._base_pad + self.get_tick_padding()
+        self.stale = True
+
+    def _get_text1(self):
+        'Get the default Text instance'
+        # the y loc is 3 points below the min of y axis
+        # get the affine as an a, b, c, d, tx, ty list
+        # x in data coords, y in axes coords
+        trans, vert, horiz = self._get_text1_transform()
+        t = mtext.Text(
+            x=0, y=0,
+            fontproperties=font_manager.FontProperties(size=self._labelsize),
+            color=self._labelcolor,
+            verticalalignment=vert,
+            horizontalalignment=horiz,
+            )
+        t.set_transform(trans)
+        self._set_artist_props(t)
+        return t
+
+    def _get_text2(self):
+        'Get the default Text 2 instance'
+        # x in data coords, y in axes coords
+        trans, vert, horiz = self._get_text2_transform()
+        t = mtext.Text(
+            x=0, y=1,
+            fontproperties=font_manager.FontProperties(size=self._labelsize),
+            color=self._labelcolor,
+            verticalalignment=vert,
+            horizontalalignment=horiz,
+            )
+        t.set_transform(trans)
+        self._set_artist_props(t)
+        return t
+
+    def _get_tick1line(self):
+        'Get the default line2D instance'
+        # x in data coords, y in axes coords
+        l = mlines.Line2D(xdata=(0,), ydata=(0,), color=self._color,
+                          linestyle='None', marker=self._tickmarkers[0],
+                          markersize=self._size,
+                          markeredgewidth=self._width, zorder=self._zorder)
+        l.set_transform(self.axes.get_xaxis_transform(which='tick1'))
+        self._set_artist_props(l)
+        return l
+
+    def _get_tick2line(self):
+        'Get the default line2D instance'
+        # x in data coords, y in axes coords
+        l = mlines.Line2D(xdata=(0,), ydata=(1,),
+                          color=self._color,
+                          linestyle='None',
+                          marker=self._tickmarkers[1],
+                          markersize=self._size,
+                          markeredgewidth=self._width,
+                          zorder=self._zorder)
+
+        l.set_transform(self.axes.get_xaxis_transform(which='tick2'))
+        self._set_artist_props(l)
+        return l
+
+    def _get_gridline(self):
+        'Get the default line2D instance'
+        # x in data coords, y in axes coords
+        l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0),
+                          color=self._grid_color,
+                          linestyle=self._grid_linestyle,
+                          linewidth=self._grid_linewidth,
+                          alpha=self._grid_alpha,
+                          markersize=0,
+                          **self._grid_kw)
+        l.set_transform(self.axes.get_xaxis_transform(which='grid'))
+        l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
+        self._set_artist_props(l)
+
+        return l
+
+    def update_position(self, loc):
+        """Set the location of tick in data coords with scalar *loc*."""
+        self.tick1line.set_xdata((loc,))
+        self.tick2line.set_xdata((loc,))
+        self.gridline.set_xdata((loc,))
+        self.label1.set_x(loc)
+        self.label2.set_x(loc)
+        self._loc = loc
+        self.stale = True
+
+    def get_view_interval(self):
+        # docstring inherited
+        return self.axes.viewLim.intervalx
+
+
+class YTick(Tick):
+    """
+    Contains all the Artists needed to make a Y tick - the tick line,
+    the label text and the grid line
+    """
+    __name__ = 'ytick'
+
+    def _get_text1_transform(self):
+        return self.axes.get_yaxis_text1_transform(self._pad)
+
+    def _get_text2_transform(self):
+        return self.axes.get_yaxis_text2_transform(self._pad)
+
+    def apply_tickdir(self, tickdir):
+        if tickdir is None:
+            tickdir = rcParams['%s.direction' % self.__name__.lower()]
+        self._tickdir = tickdir
+
+        if self._tickdir == 'in':
+            self._tickmarkers = (mlines.TICKRIGHT, mlines.TICKLEFT)
+        elif self._tickdir == 'inout':
+            self._tickmarkers = ('_', '_')
+        else:
+            self._tickmarkers = (mlines.TICKLEFT, mlines.TICKRIGHT)
+        self._pad = self._base_pad + self.get_tick_padding()
+        self.stale = True
+
+    # how far from the y axis line the right of the ticklabel are
+    def _get_text1(self):
+        'Get the default Text instance'
+        # x in axes coords, y in data coords
+        trans, vert, horiz = self._get_text1_transform()
+        t = mtext.Text(
+            x=0, y=0,
+            fontproperties=font_manager.FontProperties(size=self._labelsize),
+            color=self._labelcolor,
+            verticalalignment=vert,
+            horizontalalignment=horiz,
+            )
+        t.set_transform(trans)
+        self._set_artist_props(t)
+        return t
+
+    def _get_text2(self):
+        'Get the default Text instance'
+        # x in axes coords, y in data coords
+        trans, vert, horiz = self._get_text2_transform()
+        t = mtext.Text(
+            x=1, y=0,
+            fontproperties=font_manager.FontProperties(size=self._labelsize),
+            color=self._labelcolor,
+            verticalalignment=vert,
+            horizontalalignment=horiz,
+            )
+        t.set_transform(trans)
+        self._set_artist_props(t)
+        return t
+
+    def _get_tick1line(self):
+        'Get the default line2D instance'
+        # x in axes coords, y in data coords
+
+        l = mlines.Line2D((0,), (0,),
+                          color=self._color,
+                          marker=self._tickmarkers[0],
+                          linestyle='None',
+                          markersize=self._size,
+                          markeredgewidth=self._width,
+                          zorder=self._zorder)
+        l.set_transform(self.axes.get_yaxis_transform(which='tick1'))
+        self._set_artist_props(l)
+        return l
+
+    def _get_tick2line(self):
+        'Get the default line2D instance'
+        # x in axes coords, y in data coords
+        l = mlines.Line2D((1,), (0,),
+                          color=self._color,
+                          marker=self._tickmarkers[1],
+                          linestyle='None',
+                          markersize=self._size,
+                          markeredgewidth=self._width,
+                          zorder=self._zorder)
+        l.set_transform(self.axes.get_yaxis_transform(which='tick2'))
+        self._set_artist_props(l)
+        return l
+
+    def _get_gridline(self):
+        'Get the default line2D instance'
+        # x in axes coords, y in data coords
+        l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0),
+                          color=self._grid_color,
+                          linestyle=self._grid_linestyle,
+                          linewidth=self._grid_linewidth,
+                          alpha=self._grid_alpha,
+                          markersize=0,
+                          **self._grid_kw)
+        l.set_transform(self.axes.get_yaxis_transform(which='grid'))
+        l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
+        self._set_artist_props(l)
+        return l
+
+    def update_position(self, loc):
+        """Set the location of tick in data coords with scalar *loc*."""
+        self.tick1line.set_ydata((loc,))
+        self.tick2line.set_ydata((loc,))
+        self.gridline.set_ydata((loc,))
+        self.label1.set_y(loc)
+        self.label2.set_y(loc)
+        self._loc = loc
+        self.stale = True
+
+    def get_view_interval(self):
+        # docstring inherited
+        return self.axes.viewLim.intervaly
+
+
+class Ticker:
+    """
+    A container for the objects defining tick position and format.
+
+    Attributes
+    ----------
+    locator : `matplotlib.ticker.Locator` subclass
+        Determines the positions of the ticks.
+    formatter : `matplotlib.ticker.Formatter` subclass
+        Determines the format of the tick labels.
+    """
+
+    def __init__(self):
+        self._locator = None
+        self._formatter = None
+
+    @property
+    def locator(self):
+        return self._locator
+
+    @locator.setter
+    def locator(self, locator):
+        if not isinstance(locator, mticker.Locator):
+            cbook.warn_deprecated(
+                "3.2", message="Support for locators that do not subclass "
+                "matplotlib.ticker.Locator is deprecated since %(since)s and "
+                "support for them will be removed %(removal)s.")
+        self._locator = locator
+
+    @property
+    def formatter(self):
+        return self._formatter
+
+    @formatter.setter
+    def formatter(self, formatter):
+        if not isinstance(formatter, mticker.Formatter):
+            cbook.warn_deprecated(
+                "3.2", message="Support for formatters that do not subclass "
+                "matplotlib.ticker.Formatter is deprecated since %(since)s "
+                "and support for them will be removed %(removal)s.")
+        self._formatter = formatter
+
+
+class _LazyTickList:
+    """
+    A descriptor for lazy instantiation of tick lists.
+
+    See comment above definition of the ``majorTicks`` and ``minorTicks``
+    attributes.
+    """
+
+    def __init__(self, major):
+        self._major = major
+
+    def __get__(self, instance, cls):
+        if instance is None:
+            return self
+        else:
+            # instance._get_tick() can itself try to access the majorTicks
+            # attribute (e.g. in certain projection classes which override
+            # e.g. get_xaxis_text1_transform).  In order to avoid infinite
+            # recursion, first set the majorTicks on the instance to an empty
+            # list, then create the tick and append it.
+            if self._major:
+                instance.majorTicks = []
+                tick = instance._get_tick(major=True)
+                instance.majorTicks.append(tick)
+                return instance.majorTicks
+            else:
+                instance.minorTicks = []
+                tick = instance._get_tick(major=False)
+                instance.minorTicks.append(tick)
+                return instance.minorTicks
+
+
+class Axis(martist.Artist):
+    """
+    Base class for `.XAxis` and `.YAxis`.
+
+    Attributes
+    ----------
+    isDefault_label : bool
+
+    axes : `matplotlib.axes.Axes`
+        The `~.axes.Axes` to which the Axis belongs.
+    major : `matplotlib.axis.Ticker`
+        Determines the major tick positions and their label format.
+    minor : `matplotlib.axis.Ticker`
+        Determines the minor tick positions and their label format.
+    callbacks : `matplotlib.cbook.CallbackRegistry`
+
+    label : `.Text`
+        The axis label.
+    labelpad : float
+        The distance between the axis label and the tick labels.
+        Defaults to :rc:`axes.labelpad` = 4.
+    offsetText : `.Text`
+        A `.Text` object containing the data offset of the ticks (if any).
+    pickradius : float
+        The acceptance radius for containment tests. See also `.Axis.contains`.
+    majorTicks : list of `.Tick`
+        The major ticks.
+    minorTicks : list of `.Tick`
+        The minor ticks.
+    """
+    OFFSETTEXTPAD = 3
+
+    def __str__(self):
+        return "{}({},{})".format(
+            type(self).__name__, *self.axes.transAxes.transform((0, 0)))
+
+    def __init__(self, axes, pickradius=15):
+        """
+        Parameters
+        ----------
+        axes : `matplotlib.axes.Axes`
+            The `~.axes.Axes` to which the created Axis belongs.
+        pickradius : float
+            The acceptance radius for containment tests. See also
+            `.Axis.contains`.
+        """
+        martist.Artist.__init__(self)
+        self._remove_overlapping_locs = True
+
+        self.set_figure(axes.figure)
+
+        self.isDefault_label = True
+
+        self.axes = axes
+        self.major = Ticker()
+        self.minor = Ticker()
+        self.callbacks = cbook.CallbackRegistry()
+
+        self._autolabelpos = True
+        self._smart_bounds = False  # Deprecated in 3.2
+
+        self.label = self._get_label()
+        self.labelpad = rcParams['axes.labelpad']
+        self.offsetText = self._get_offset_text()
+
+        self.pickradius = pickradius
+
+        # Initialize here for testing; later add API
+        self._major_tick_kw = dict()
+        self._minor_tick_kw = dict()
+
+        self.cla()
+        self._set_scale('linear')
+
+    # During initialization, Axis objects often create ticks that are later
+    # unused; this turns out to be a very slow step.  Instead, use a custom
+    # descriptor to make the tick lists lazy and instantiate them as needed.
+    majorTicks = _LazyTickList(major=True)
+    minorTicks = _LazyTickList(major=False)
+
+    def get_remove_overlapping_locs(self):
+        return self._remove_overlapping_locs
+
+    def set_remove_overlapping_locs(self, val):
+        self._remove_overlapping_locs = bool(val)
+
+    remove_overlapping_locs = property(
+        get_remove_overlapping_locs, set_remove_overlapping_locs,
+        doc=('If minor ticker locations that overlap with major '
+             'ticker locations should be trimmed.'))
+
+    def set_label_coords(self, x, y, transform=None):
+        """
+        Set the coordinates of the label.
+
+        By default, the x coordinate of the y label and the y coordinate of the
+        x label are determined by the tick label bounding boxes, but this can
+        lead to poor alignment of multiple labels if there are multiple axes.
+
+        You can also specify the coordinate system of the label with the
+        transform.  If None, the default coordinate system will be the axes
+        coordinate system: (0, 0) is bottom left, (0.5, 0.5) is center, etc.
+        """
+        self._autolabelpos = False
+        if transform is None:
+            transform = self.axes.transAxes
+
+        self.label.set_transform(transform)
+        self.label.set_position((x, y))
+        self.stale = True
+
+    def get_transform(self):
+        return self._scale.get_transform()
+
+    def get_scale(self):
+        return self._scale.name
+
+    def _set_scale(self, value, **kwargs):
+        self._scale = mscale.scale_factory(value, self, **kwargs)
+        self._scale.set_default_locators_and_formatters(self)
+
+        self.isDefault_majloc = True
+        self.isDefault_minloc = True
+        self.isDefault_majfmt = True
+        self.isDefault_minfmt = True
+
+    def limit_range_for_scale(self, vmin, vmax):
+        return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())
+
+    def get_children(self):
+        return [self.label, self.offsetText,
+                *self.get_major_ticks(), *self.get_minor_ticks()]
+
+    def cla(self):
+        """Clear this axis."""
+
+        self.label.set_text('')  # self.set_label_text would change isDefault_
+
+        self._set_scale('linear')
+
+        # Clear the callback registry for this axis, or it may "leak"
+        self.callbacks = cbook.CallbackRegistry()
+
+        # whether the grids are on
+        self._gridOnMajor = (rcParams['axes.grid'] and
+                             rcParams['axes.grid.which'] in ('both', 'major'))
+        self._gridOnMinor = (rcParams['axes.grid'] and
+                             rcParams['axes.grid.which'] in ('both', 'minor'))
+
+        self.reset_ticks()
+
+        self.converter = None
+        self.units = None
+        self.set_units(None)
+        self.stale = True
+
+    def reset_ticks(self):
+        """
+        Re-initialize the major and minor Tick lists.
+
+        Each list starts with a single fresh Tick.
+        """
+        # Restore the lazy tick lists.
+        try:
+            del self.majorTicks
+        except AttributeError:
+            pass
+        try:
+            del self.minorTicks
+        except AttributeError:
+            pass
+        try:
+            self.set_clip_path(self.axes.patch)
+        except AttributeError:
+            pass
+
+    def set_tick_params(self, which='major', reset=False, **kw):
+        """
+        Set appearance parameters for ticks, ticklabels, and gridlines.
+
+        For documentation of keyword arguments, see
+        :meth:`matplotlib.axes.Axes.tick_params`.
+        """
+        dicts = []
+        if which == 'major' or which == 'both':
+            dicts.append(self._major_tick_kw)
+        if which == 'minor' or which == 'both':
+            dicts.append(self._minor_tick_kw)
+        kwtrans = self._translate_tick_kw(kw)
+
+        # this stashes the parameter changes so any new ticks will
+        # automatically get them
+        for d in dicts:
+            if reset:
+                d.clear()
+            d.update(kwtrans)
+
+        if reset:
+            self.reset_ticks()
+        else:
+            # apply the new kwargs to the existing ticks
+            if which == 'major' or which == 'both':
+                for tick in self.majorTicks:
+                    tick._apply_params(**kwtrans)
+            if which == 'minor' or which == 'both':
+                for tick in self.minorTicks:
+                    tick._apply_params(**kwtrans)
+            # special-case label color to also apply to the offset
+            # text
+            if 'labelcolor' in kwtrans:
+                self.offsetText.set_color(kwtrans['labelcolor'])
+
+        self.stale = True
+
+    @staticmethod
+    def _translate_tick_kw(kw):
+        # The following lists may be moved to a more accessible location.
+        kwkeys = ['size', 'width', 'color', 'tickdir', 'pad',
+                  'labelsize', 'labelcolor', 'zorder', 'gridOn',
+                  'tick1On', 'tick2On', 'label1On', 'label2On',
+                  'length', 'direction', 'left', 'bottom', 'right', 'top',
+                  'labelleft', 'labelbottom', 'labelright', 'labeltop',
+                  'labelrotation'] + _gridline_param_names
+        kwtrans = {}
+        if 'length' in kw:
+            kwtrans['size'] = kw.pop('length')
+        if 'direction' in kw:
+            kwtrans['tickdir'] = kw.pop('direction')
+        if 'rotation' in kw:
+            kwtrans['labelrotation'] = kw.pop('rotation')
+        if 'left' in kw:
+            kwtrans['tick1On'] = kw.pop('left')
+        if 'bottom' in kw:
+            kwtrans['tick1On'] = kw.pop('bottom')
+        if 'right' in kw:
+            kwtrans['tick2On'] = kw.pop('right')
+        if 'top' in kw:
+            kwtrans['tick2On'] = kw.pop('top')
+        if 'labelleft' in kw:
+            kwtrans['label1On'] = kw.pop('labelleft')
+        if 'labelbottom' in kw:
+            kwtrans['label1On'] = kw.pop('labelbottom')
+        if 'labelright' in kw:
+            kwtrans['label2On'] = kw.pop('labelright')
+        if 'labeltop' in kw:
+            kwtrans['label2On'] = kw.pop('labeltop')
+        if 'colors' in kw:
+            c = kw.pop('colors')
+            kwtrans['color'] = c
+            kwtrans['labelcolor'] = c
+        # Maybe move the checking up to the caller of this method.
+        for key in kw:
+            if key not in kwkeys:
+                raise ValueError(
+                    "keyword %s is not recognized; valid keywords are %s"
+                    % (key, kwkeys))
+            kwtrans.update(kw)
+        return kwtrans
+
+    def set_clip_path(self, clippath, transform=None):
+        martist.Artist.set_clip_path(self, clippath, transform)
+        for child in self.majorTicks + self.minorTicks:
+            child.set_clip_path(clippath, transform)
+        self.stale = True
+
+    def get_view_interval(self):
+        """Return the view limits ``(min, max)`` of this axis."""
+        raise NotImplementedError('Derived must override')
+
+    def set_view_interval(self, vmin, vmax, ignore=False):
+        """
+        Set the axis view limits.  This method is for internal use; Matplotlib
+        users should typically use e.g. `~Axes.set_xlim` and `~Axes.set_ylim`.
+
+        If *ignore* is False (the default), this method will never reduce the
+        preexisting view limits, only expand them if *vmin* or *vmax* are not
+        within them.  Moreover, the order of *vmin* and *vmax* does not matter;
+        the orientation of the axis will not change.
+
+        If *ignore* is True, the view limits will be set exactly to ``(vmin,
+        vmax)`` in that order.
+        """
+        raise NotImplementedError('Derived must override')
+
+    def get_data_interval(self):
+        """Return the Interval instance for this axis data limits."""
+        raise NotImplementedError('Derived must override')
+
+    def set_data_interval(self, vmin, vmax, ignore=False):
+        """
+        Set the axis data limits.  This method is for internal use.
+
+        If *ignore* is False (the default), this method will never reduce the
+        preexisting data limits, only expand them if *vmin* or *vmax* are not
+        within them.  Moreover, the order of *vmin* and *vmax* does not matter;
+        the orientation of the axis will not change.
+
+        If *ignore* is True, the data limits will be set exactly to ``(vmin,
+        vmax)`` in that order.
+        """
+        raise NotImplementedError('Derived must override')
+
+    def get_inverted(self):
+        """
+        Return whether the axis is oriented in the "inverse" direction.
+
+        The "normal" direction is increasing to the right for the x-axis and to
+        the top for the y-axis; the "inverse" direction is increasing to the
+        left for the x-axis and to the bottom for the y-axis.
+        """
+        low, high = self.get_view_interval()
+        return high < low
+
+    def set_inverted(self, inverted):
+        """
+        Set whether the axis is oriented in the "inverse" direction.
+
+        The "normal" direction is increasing to the right for the x-axis and to
+        the top for the y-axis; the "inverse" direction is increasing to the
+        left for the x-axis and to the bottom for the y-axis.
+        """
+        # Currently, must be implemented in subclasses using set_xlim/set_ylim
+        # rather than generically using set_view_interval, so that shared
+        # axes get updated as well.
+        raise NotImplementedError('Derived must override')
+
+    def set_default_intervals(self):
+        """
+        Set the default limits for the axis data and view interval if they
+        have not been not mutated yet.
+        """
+        # this is mainly in support of custom object plotting.  For
+        # example, if someone passes in a datetime object, we do not
+        # know automagically how to set the default min/max of the
+        # data and view limits.  The unit conversion AxisInfo
+        # interface provides a hook for custom types to register
+        # default limits through the AxisInfo.default_limits
+        # attribute, and the derived code below will check for that
+        # and use it if is available (else just use 0..1)
+
+    def _set_artist_props(self, a):
+        if a is None:
+            return
+        a.set_figure(self.figure)
+
+    @cbook.deprecated("3.1")
+    def iter_ticks(self):
+        """
+        Yield ``(Tick, location, label)`` tuples for major and minor ticks.
+        """
+        major_locs = self.get_majorticklocs()
+        major_labels = self.major.formatter.format_ticks(major_locs)
+        major_ticks = self.get_major_ticks(len(major_locs))
+        yield from zip(major_ticks, major_locs, major_labels)
+        minor_locs = self.get_minorticklocs()
+        minor_labels = self.minor.formatter.format_ticks(minor_locs)
+        minor_ticks = self.get_minor_ticks(len(minor_locs))
+        yield from zip(minor_ticks, minor_locs, minor_labels)
+
+    def get_ticklabel_extents(self, renderer):
+        """
+        Get the extents of the tick labels on either side
+        of the axes.
+        """
+
+        ticks_to_draw = self._update_ticks()
+        ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
+                                                                renderer)
+
+        if len(ticklabelBoxes):
+            bbox = mtransforms.Bbox.union(ticklabelBoxes)
+        else:
+            bbox = mtransforms.Bbox.from_extents(0, 0, 0, 0)
+        if len(ticklabelBoxes2):
+            bbox2 = mtransforms.Bbox.union(ticklabelBoxes2)
+        else:
+            bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
+        return bbox, bbox2
+
+    @cbook.deprecated("3.2")
+    def set_smart_bounds(self, value):
+        """Set the axis to have smart bounds."""
+        self._smart_bounds = value
+        self.stale = True
+
+    @cbook.deprecated("3.2")
+    def get_smart_bounds(self):
+        """Return whether the axis has smart bounds."""
+        return self._smart_bounds
+
+    def _update_ticks(self):
+        """
+        Update ticks (position and labels) using the current data interval of
+        the axes.  Return the list of ticks that will be drawn.
+        """
+        major_locs = self.get_majorticklocs()
+        major_labels = self.major.formatter.format_ticks(major_locs)
+        major_ticks = self.get_major_ticks(len(major_locs))
+        self.major.formatter.set_locs(major_locs)
+        for tick, loc, label in zip(major_ticks, major_locs, major_labels):
+            tick.update_position(loc)
+            tick.set_label1(label)
+            tick.set_label2(label)
+        minor_locs = self.get_minorticklocs()
+        minor_labels = self.minor.formatter.format_ticks(minor_locs)
+        minor_ticks = self.get_minor_ticks(len(minor_locs))
+        self.minor.formatter.set_locs(minor_locs)
+        for tick, loc, label in zip(minor_ticks, minor_locs, minor_labels):
+            tick.update_position(loc)
+            tick.set_label1(label)
+            tick.set_label2(label)
+        ticks = [*major_ticks, *minor_ticks]
+
+        view_low, view_high = self.get_view_interval()
+        if view_low > view_high:
+            view_low, view_high = view_high, view_low
+
+        if self._smart_bounds and ticks:  # _smart_bounds is deprecated in 3.2
+            # handle inverted limits
+            data_low, data_high = sorted(self.get_data_interval())
+            locs = np.sort([tick.get_loc() for tick in ticks])
+            if data_low <= view_low:
+                # data extends beyond view, take view as limit
+                ilow = view_low
+            else:
+                # data stops within view, take best tick
+                good_locs = locs[locs <= data_low]
+                if len(good_locs):
+                    # last tick prior or equal to first data point
+                    ilow = good_locs[-1]
+                else:
+                    # No ticks (why not?), take first tick
+                    ilow = locs[0]
+            if data_high >= view_high:
+                # data extends beyond view, take view as limit
+                ihigh = view_high
+            else:
+                # data stops within view, take best tick
+                good_locs = locs[locs >= data_high]
+                if len(good_locs):
+                    # first tick after or equal to last data point
+                    ihigh = good_locs[0]
+                else:
+                    # No ticks (why not?), take last tick
+                    ihigh = locs[-1]
+            ticks = [tick for tick in ticks if ilow <= tick.get_loc() <= ihigh]
+
+        interval_t = self.get_transform().transform([view_low, view_high])
+
+        ticks_to_draw = []
+        for tick in ticks:
+            try:
+                loc_t = self.get_transform().transform(tick.get_loc())
+            except AssertionError:
+                # transforms.transform doesn't allow masked values but
+                # some scales might make them, so we need this try/except.
+                pass
+            else:
+                if mtransforms._interval_contains_close(interval_t, loc_t):
+                    ticks_to_draw.append(tick)
+
+        return ticks_to_draw
+
+    def _get_tick_bboxes(self, ticks, renderer):
+        """Return lists of bboxes for ticks' label1's and label2's."""
+        return ([tick.label1.get_window_extent(renderer)
+                 for tick in ticks if tick.label1.get_visible()],
+                [tick.label2.get_window_extent(renderer)
+                 for tick in ticks if tick.label2.get_visible()])
+
+    def get_tightbbox(self, renderer):
+        """
+        Return a bounding box that encloses the axis. It only accounts
+        tick labels, axis label, and offsetText.
+        """
+        if not self.get_visible():
+            return
+
+        ticks_to_draw = self._update_ticks()
+
+        self._update_label_position(renderer)
+
+        # go back to just this axis's tick labels
+        ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(
+                    ticks_to_draw, renderer)
+
+        self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
+        self.offsetText.set_text(self.major.formatter.get_offset())
+
+        bboxes = [
+            *(a.get_window_extent(renderer)
+              for a in [self.label, self.offsetText]
+              if a.get_visible()),
+            *ticklabelBoxes,
+            *ticklabelBoxes2,
+        ]
+        bboxes = [b for b in bboxes
+                  if 0 < b.width < np.inf and 0 < b.height < np.inf]
+        if bboxes:
+            return mtransforms.Bbox.union(bboxes)
+        else:
+            return None
+
+    def get_tick_padding(self):
+        values = []
+        if len(self.majorTicks):
+            values.append(self.majorTicks[0].get_tick_padding())
+        if len(self.minorTicks):
+            values.append(self.minorTicks[0].get_tick_padding())
+        return max(values, default=0)
+
+    @martist.allow_rasterization
+    def draw(self, renderer, *args, **kwargs):
+        'Draw the axis lines, grid lines, tick lines and labels'
+
+        if not self.get_visible():
+            return
+        renderer.open_group(__name__, gid=self.get_gid())
+
+        ticks_to_draw = self._update_ticks()
+        ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
+                                                                renderer)
+
+        for tick in ticks_to_draw:
+            tick.draw(renderer)
+
+        # scale up the axis label box to also find the neighbors, not
+        # just the tick labels that actually overlap note we need a
+        # *copy* of the axis label box because we don't wan't to scale
+        # the actual bbox
+
+        self._update_label_position(renderer)
+
+        self.label.draw(renderer)
+
+        self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
+        self.offsetText.set_text(self.major.formatter.get_offset())
+        self.offsetText.draw(renderer)
+
+        renderer.close_group(__name__)
+        self.stale = False
+
+    def _get_label(self):
+        raise NotImplementedError('Derived must override')
+
+    def _get_offset_text(self):
+        raise NotImplementedError('Derived must override')
+
+    def get_gridlines(self):
+        'Return the grid lines as a list of Line2D instance'
+        ticks = self.get_major_ticks()
+        return cbook.silent_list('Line2D gridline',
+                                 [tick.gridline for tick in ticks])
+
+    def get_label(self):
+        'Return the axis label as a Text instance'
+        return self.label
+
+    def get_offset_text(self):
+        'Return the axis offsetText as a Text instance'
+        return self.offsetText
+
+    def get_pickradius(self):
+        'Return the depth of the axis used by the picker'
+        return self.pickradius
+
+    def get_majorticklabels(self):
+        'Return a list of Text instances for the major ticklabels.'
+        ticks = self.get_major_ticks()
+        labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
+        labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
+        return cbook.silent_list('Text major ticklabel', labels1 + labels2)
+
+    def get_minorticklabels(self):
+        'Return a list of Text instances for the minor ticklabels.'
+        ticks = self.get_minor_ticks()
+        labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
+        labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
+        return cbook.silent_list('Text minor ticklabel', labels1 + labels2)
+
+    def get_ticklabels(self, minor=False, which=None):
+        """
+        Get the tick labels as a list of `~matplotlib.text.Text` instances.
+
+        Parameters
+        ----------
+        minor : bool
+           If True return the minor ticklabels,
+           else return the major ticklabels
+
+        which : None, ('minor', 'major', 'both')
+           Overrides *minor*.
+
+           Selects which ticklabels to return
+
+        Returns
+        -------
+        ret : list
+           List of `~matplotlib.text.Text` instances.
+        """
+
+        if which is not None:
+            if which == 'minor':
+                return self.get_minorticklabels()
+            elif which == 'major':
+                return self.get_majorticklabels()
+            elif which == 'both':
+                return self.get_majorticklabels() + self.get_minorticklabels()
+            else:
+                cbook._check_in_list(['major', 'minor', 'both'], which=which)
+        if minor:
+            return self.get_minorticklabels()
+        return self.get_majorticklabels()
+
+    def get_majorticklines(self):
+        'Return the major tick lines as a list of Line2D instances'
+        lines = []
+        ticks = self.get_major_ticks()
+        for tick in ticks:
+            lines.append(tick.tick1line)
+            lines.append(tick.tick2line)
+        return cbook.silent_list('Line2D ticklines', lines)
+
+    def get_minorticklines(self):
+        'Return the minor tick lines as a list of Line2D instances'
+        lines = []
+        ticks = self.get_minor_ticks()
+        for tick in ticks:
+            lines.append(tick.tick1line)
+            lines.append(tick.tick2line)
+        return cbook.silent_list('Line2D ticklines', lines)
+
+    def get_ticklines(self, minor=False):
+        'Return the tick lines as a list of Line2D instances'
+        if minor:
+            return self.get_minorticklines()
+        return self.get_majorticklines()
+
+    def get_majorticklocs(self):
+        """Get the array of major tick locations in data coordinates."""
+        return self.major.locator()
+
+    def get_minorticklocs(self):
+        """Get the array of minor tick locations in data coordinates."""
+        # Remove minor ticks duplicating major ticks.
+        major_locs = self.major.locator()
+        minor_locs = self.minor.locator()
+        transform = self._scale.get_transform()
+        tr_minor_locs = transform.transform(minor_locs)
+        tr_major_locs = transform.transform(major_locs)
+        lo, hi = sorted(transform.transform(self.get_view_interval()))
+        # Use the transformed view limits as scale.  1e-5 is the default rtol
+        # for np.isclose.
+        tol = (hi - lo) * 1e-5
+        if self.remove_overlapping_locs:
+            minor_locs = [
+                loc for loc, tr_loc in zip(minor_locs, tr_minor_locs)
+                if ~np.isclose(tr_loc, tr_major_locs, atol=tol, rtol=0).any()]
+        return minor_locs
+
+    def get_ticklocs(self, minor=False):
+        """Get the array of tick locations in data coordinates."""
+        return self.get_minorticklocs() if minor else self.get_majorticklocs()
+
+    def get_ticks_direction(self, minor=False):
+        """
+        Get the tick directions as a numpy array
+
+        Parameters
+        ----------
+        minor : boolean
+            True to return the minor tick directions,
+            False to return the major tick directions,
+            Default is False
+
+        Returns
+        -------
+        numpy array of tick directions
+        """
+        if minor:
+            return np.array(
+                [tick._tickdir for tick in self.get_minor_ticks()])
+        else:
+            return np.array(
+                [tick._tickdir for tick in self.get_major_ticks()])
+
+    def _get_tick(self, major):
+        """Return the default tick instance."""
+        raise NotImplementedError('derived must override')
+
+    def _copy_tick_props(self, src, dest):
+        """Copy the properties from *src* tick to *dest* tick."""
+        if src is None or dest is None:
+            return
+        dest.label1.update_from(src.label1)
+        dest.label2.update_from(src.label2)
+        dest.tick1line.update_from(src.tick1line)
+        dest.tick2line.update_from(src.tick2line)
+        dest.gridline.update_from(src.gridline)
+
+    def get_label_text(self):
+        'Get the text of the label'
+        return self.label.get_text()
+
+    def get_major_locator(self):
+        'Get the locator of the major ticker'
+        return self.major.locator
+
+    def get_minor_locator(self):
+        'Get the locator of the minor ticker'
+        return self.minor.locator
+
+    def get_major_formatter(self):
+        'Get the formatter of the major ticker'
+        return self.major.formatter
+
+    def get_minor_formatter(self):
+        'Get the formatter of the minor ticker'
+        return self.minor.formatter
+
+    def get_major_ticks(self, numticks=None):
+        'Get the tick instances; grow as necessary.'
+        if numticks is None:
+            numticks = len(self.get_majorticklocs())
+
+        while len(self.majorTicks) < numticks:
+            # Update the new tick label properties from the old.
+            tick = self._get_tick(major=True)
+            self.majorTicks.append(tick)
+            tick.gridline.set_visible(self._gridOnMajor)
+            self._copy_tick_props(self.majorTicks[0], tick)
+
+        return self.majorTicks[:numticks]
+
+    def get_minor_ticks(self, numticks=None):
+        'Get the minor tick instances; grow as necessary.'
+        if numticks is None:
+            numticks = len(self.get_minorticklocs())
+
+        while len(self.minorTicks) < numticks:
+            # Update the new tick label properties from the old.
+            tick = self._get_tick(major=False)
+            self.minorTicks.append(tick)
+            tick.gridline.set_visible(self._gridOnMinor)
+            self._copy_tick_props(self.minorTicks[0], tick)
+
+        return self.minorTicks[:numticks]
+
+    def grid(self, b=None, which='major', **kwargs):
+        """
+        Configure the grid lines.
+
+        Parameters
+        ----------
+        b : bool or None
+            Whether to show the grid lines. If any *kwargs* are supplied,
+            it is assumed you want the grid on and *b* will be set to True.
+
+            If *b* is *None* and there are no *kwargs*, this toggles the
+            visibility of the lines.
+
+        which : {'major', 'minor', 'both'}
+            The grid lines to apply the changes on.
+
+        **kwargs : `.Line2D` properties
+            Define the line properties of the grid, e.g.::
+
+                grid(color='r', linestyle='-', linewidth=2)
+
+        """
+        if len(kwargs):
+            if not b and b is not None:  # something false-like but not None
+                cbook._warn_external('First parameter to grid() is false, '
+                                     'but line properties are supplied. The '
+                                     'grid will be enabled.')
+            b = True
+        which = which.lower()
+        cbook._check_in_list(['major', 'minor', 'both'], which=which)
+        gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()}
+
+        if which in ['minor', 'both']:
+            if b is None:
+                self._gridOnMinor = not self._gridOnMinor
+            else:
+                self._gridOnMinor = b
+            self.set_tick_params(which='minor', gridOn=self._gridOnMinor,
+                                 **gridkw)
+        if which in ['major', 'both']:
+            if b is None:
+                self._gridOnMajor = not self._gridOnMajor
+            else:
+                self._gridOnMajor = b
+            self.set_tick_params(which='major', gridOn=self._gridOnMajor,
+                                 **gridkw)
+        self.stale = True
+
+    def update_units(self, data):
+        """
+        Introspect *data* for units converter and update the
+        axis.converter instance if necessary. Return *True*
+        if *data* is registered for unit conversion.
+        """
+        converter = munits.registry.get_converter(data)
+        if converter is None:
+            return False
+
+        neednew = self.converter != converter
+        self.converter = converter
+        default = self.converter.default_units(data, self)
+        if default is not None and self.units is None:
+            self.set_units(default)
+
+        if neednew:
+            self._update_axisinfo()
+        self.stale = True
+        return True
+
+    def _update_axisinfo(self):
+        """
+        Check the axis converter for the stored units to see if the
+        axis info needs to be updated.
+        """
+        if self.converter is None:
+            return
+
+        info = self.converter.axisinfo(self.units, self)
+
+        if info is None:
+            return
+        if info.majloc is not None and \
+           self.major.locator != info.majloc and self.isDefault_majloc:
+            self.set_major_locator(info.majloc)
+            self.isDefault_majloc = True
+        if info.minloc is not None and \
+           self.minor.locator != info.minloc and self.isDefault_minloc:
+            self.set_minor_locator(info.minloc)
+            self.isDefault_minloc = True
+        if info.majfmt is not None and \
+           self.major.formatter != info.majfmt and self.isDefault_majfmt:
+            self.set_major_formatter(info.majfmt)
+            self.isDefault_majfmt = True
+        if info.minfmt is not None and \
+           self.minor.formatter != info.minfmt and self.isDefault_minfmt:
+            self.set_minor_formatter(info.minfmt)
+            self.isDefault_minfmt = True
+        if info.label is not None and self.isDefault_label:
+            self.set_label_text(info.label)
+            self.isDefault_label = True
+
+        self.set_default_intervals()
+
+    def have_units(self):
+        return self.converter is not None or self.units is not None
+
+    def convert_units(self, x):
+        # If x is natively supported by Matplotlib, doesn't need converting
+        if munits._is_natively_supported(x):
+            return x
+
+        if self.converter is None:
+            self.converter = munits.registry.get_converter(x)
+
+        if self.converter is None:
+            return x
+        try:
+            ret = self.converter.convert(x, self.units, self)
+        except Exception as e:
+            raise munits.ConversionError('Failed to convert value(s) to axis '
+                                         f'units: {x!r}') from e
+        return ret
+
+    def set_units(self, u):
+        """
+        Set the units for axis.
+
+        Parameters
+        ----------
+        u : units tag
+        """
+        if u == self.units:
+            return
+        self.units = u
+        self._update_axisinfo()
+        self.callbacks.process('units')
+        self.callbacks.process('units finalize')
+        self.stale = True
+
+    def get_units(self):
+        """Return the units for axis."""
+        return self.units
+
+    def set_label_text(self, label, fontdict=None, **kwargs):
+        """
+        Set the text value of the axis label.
+
+        Parameters
+        ----------
+        label : str
+            Text string.
+        fontdict : dict
+            Text properties.
+        **kwargs
+            Merged into fontdict.
+        """
+        self.isDefault_label = False
+        self.label.set_text(label)
+        if fontdict is not None:
+            self.label.update(fontdict)
+        self.label.update(kwargs)
+        self.stale = True
+        return self.label
+
+    def set_major_formatter(self, formatter):
+        """
+        Set the formatter of the major ticker.
+
+        Parameters
+        ----------
+        formatter : `~matplotlib.ticker.Formatter`
+        """
+        cbook._check_isinstance(mticker.Formatter, formatter=formatter)
+        self.isDefault_majfmt = False
+        self.major.formatter = formatter
+        formatter.set_axis(self)
+        self.stale = True
+
+    def set_minor_formatter(self, formatter):
+        """
+        Set the formatter of the minor ticker.
+
+        Parameters
+        ----------
+        formatter : `~matplotlib.ticker.Formatter`
+        """
+        cbook._check_isinstance(mticker.Formatter, formatter=formatter)
+        self.isDefault_minfmt = False
+        self.minor.formatter = formatter
+        formatter.set_axis(self)
+        self.stale = True
+
+    def set_major_locator(self, locator):
+        """
+        Set the locator of the major ticker.
+
+        Parameters
+        ----------
+        locator : `~matplotlib.ticker.Locator`
+        """
+        cbook._check_isinstance(mticker.Locator, locator=locator)
+        self.isDefault_majloc = False
+        self.major.locator = locator
+        if self.major.formatter:
+            self.major.formatter._set_locator(locator)
+        locator.set_axis(self)
+        self.stale = True
+
+    def set_minor_locator(self, locator):
+        """
+        Set the locator of the minor ticker.
+
+        Parameters
+        ----------
+        locator : `~matplotlib.ticker.Locator`
+        """
+        cbook._check_isinstance(mticker.Locator, locator=locator)
+        self.isDefault_minloc = False
+        self.minor.locator = locator
+        if self.minor.formatter:
+            self.minor.formatter._set_locator(locator)
+        locator.set_axis(self)
+        self.stale = True
+
+    def set_pickradius(self, pickradius):
+        """
+        Set the depth of the axis used by the picker.
+
+        Parameters
+        ----------
+        pickradius :  float
+        """
+        self.pickradius = pickradius
+
+    def set_ticklabels(self, ticklabels, *args, minor=False, **kwargs):
+        r"""
+        Set the text values of the tick labels.
+
+        Parameters
+        ----------
+        ticklabels : sequence of str or of `Text`\s
+            List of texts for tick labels; must include values for non-visible
+            labels.
+        minor : bool
+            If True, set minor ticks instead of major ticks.
+        **kwargs
+            Text properties.
+
+        Returns
+        -------
+        labels : list of `Text`\s
+            For each tick, includes ``tick.label1`` if it is visible, then
+            ``tick.label2`` if it is visible, in that order.
+        """
+        if args:
+            cbook.warn_deprecated(
+                "3.1", message="Additional positional arguments to "
+                "set_ticklabels are ignored, and deprecated since Matplotlib "
+                "3.1; passing them will raise a TypeError in Matplotlib 3.3.")
+        get_labels = []
+        for t in ticklabels:
+            # try calling get_text() to check whether it is Text object
+            # if it is Text, get label content
+            try:
+                get_labels.append(t.get_text())
+            # otherwise add the label to the list directly
+            except AttributeError:
+                get_labels.append(t)
+        # replace the ticklabels list with the processed one
+        ticklabels = get_labels
+
+        if minor:
+            self.set_minor_formatter(mticker.FixedFormatter(ticklabels))
+            ticks = self.get_minor_ticks()
+        else:
+            self.set_major_formatter(mticker.FixedFormatter(ticklabels))
+            ticks = self.get_major_ticks()
+        ret = []
+        for tick_label, tick in zip(ticklabels, ticks):
+            # deal with label1
+            tick.label1.set_text(tick_label)
+            tick.label1.update(kwargs)
+            # deal with label2
+            tick.label2.set_text(tick_label)
+            tick.label2.update(kwargs)
+            # only return visible tick labels
+            if tick.label1.get_visible():
+                ret.append(tick.label1)
+            if tick.label2.get_visible():
+                ret.append(tick.label2)
+
+        self.stale = True
+        return ret
+
+    @cbook._make_keyword_only("3.2", "minor")
+    def set_ticks(self, ticks, minor=False):
+        """
+        Set the locations of the tick marks from sequence ticks
+
+        Parameters
+        ----------
+        ticks : sequence of floats
+        minor : bool
+        """
+        # XXX if the user changes units, the information will be lost here
+        ticks = self.convert_units(ticks)
+        if len(ticks) > 1:
+            xleft, xright = self.get_view_interval()
+            if xright > xleft:
+                self.set_view_interval(min(ticks), max(ticks))
+            else:
+                self.set_view_interval(max(ticks), min(ticks))
+        if minor:
+            self.set_minor_locator(mticker.FixedLocator(ticks))
+            return self.get_minor_ticks(len(ticks))
+        else:
+            self.set_major_locator(mticker.FixedLocator(ticks))
+            return self.get_major_ticks(len(ticks))
+
+    def _get_tick_boxes_siblings(self, xdir, renderer):
+        """
+        Get the bounding boxes for this `.axis` and its siblings
+        as set by `.Figure.align_xlabels` or  `.Figure.align_ylablels`.
+
+        By default it just gets bboxes for self.
+        """
+        raise NotImplementedError('Derived must override')
+
+    def _update_label_position(self, renderer):
+        """
+        Update the label position based on the bounding box enclosing
+        all the ticklabels and axis spine.
+        """
+        raise NotImplementedError('Derived must override')
+
+    def _update_offset_text_position(self, bboxes, bboxes2):
+        """
+        Update the offset text position based on the sequence of bounding
+        boxes of all the ticklabels.
+        """
+        raise NotImplementedError('Derived must override')
+
+    def pan(self, numsteps):
+        """Pan by *numsteps* (can be positive or negative)."""
+        self.major.locator.pan(numsteps)
+
+    def zoom(self, direction):
+        """Zoom in/out on axis; if *direction* is >0 zoom in, else zoom out."""
+        self.major.locator.zoom(direction)
+
+    def axis_date(self, tz=None):
+        """
+        Sets up axis ticks and labels treating data along this axis as dates.
+
+        Parameters
+        ----------
+        tz : tzinfo or str or None
+            The timezone used to create date labels.
+        """
+        # By providing a sample datetime instance with the desired timezone,
+        # the registered converter can be selected, and the "units" attribute,
+        # which is the timezone, can be set.
+        if isinstance(tz, str):
+            import dateutil.tz
+            tz = dateutil.tz.gettz(tz)
+        self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
+
+    def get_tick_space(self):
+        """Return the estimated number of ticks that can fit on the axis."""
+        # Must be overridden in the subclass
+        raise NotImplementedError()
+
+    def _get_ticks_position(self):
+        """
+        Helper for `XAxis.get_ticks_position` and `YAxis.get_ticks_position`.
+
+        Check the visibility of tick1line, label1, tick2line, and label2 on
+        the first major and the first minor ticks, and return
+
+        - 1 if only tick1line and label1 are visible (which corresponds to
+          "bottom" for the x-axis and "left" for the y-axis);
+        - 2 if only tick2line and label2 are visible (which corresponds to
+          "top" for the x-axis and "right" for the y-axis);
+        - "default" if only tick1line, tick2line and label1 are visible;
+        - "unknown" otherwise.
+        """
+        major = self.majorTicks[0]
+        minor = self.minorTicks[0]
+        if all(tick.tick1line.get_visible()
+               and not tick.tick2line.get_visible()
+               and tick.label1.get_visible()
+               and not tick.label2.get_visible()
+               for tick in [major, minor]):
+            return 1
+        elif all(tick.tick2line.get_visible()
+                 and not tick.tick1line.get_visible()
+                 and tick.label2.get_visible()
+                 and not tick.label1.get_visible()
+                 for tick in [major, minor]):
+            return 2
+        elif all(tick.tick1line.get_visible()
+                 and tick.tick2line.get_visible()
+                 and tick.label1.get_visible()
+                 and not tick.label2.get_visible()
+                 for tick in [major, minor]):
+            return "default"
+        else:
+            return "unknown"
+
+    def get_label_position(self):
+        """
+        Return the label position (top or bottom)
+        """
+        return self.label_position
+
+    def set_label_position(self, position):
+        """
+        Set the label position (top or bottom)
+
+        Parameters
+        ----------
+        position : {'top', 'bottom'}
+        """
+        raise NotImplementedError()
+
+    def get_minpos(self):
+        raise NotImplementedError()
+
+
+def _make_getset_interval(method_name, lim_name, attr_name):
+    """
+    Helper to generate ``get_{data,view}_interval`` and
+    ``set_{data,view}_interval`` implementations.
+    """
+
+    def getter(self):
+        # docstring inherited.
+        return getattr(getattr(self.axes, lim_name), attr_name)
+
+    def setter(self, vmin, vmax, ignore=False):
+        # docstring inherited.
+        if ignore:
+            setattr(getattr(self.axes, lim_name), attr_name, (vmin, vmax))
+        else:
+            oldmin, oldmax = getter(self)
+            if oldmin < oldmax:
+                setter(self, min(vmin, vmax, oldmin), max(vmin, vmax, oldmax),
+                       ignore=True)
+            else:
+                setter(self, max(vmin, vmax, oldmin), min(vmin, vmax, oldmax),
+                       ignore=True)
+        self.stale = True
+
+    getter.__name__ = f"get_{method_name}_interval"
+    setter.__name__ = f"set_{method_name}_interval"
+
+    return getter, setter
+
+
+class XAxis(Axis):
+    __name__ = 'xaxis'
+    axis_name = 'x'  #: Read-only name identifying the axis.
+
+    def contains(self, mouseevent):
+        """Test whether the mouse event occurred in the x axis.
+        """
+        inside, info = self._default_contains(mouseevent)
+        if inside is not None:
+            return inside, info
+
+        x, y = mouseevent.x, mouseevent.y
+        try:
+            trans = self.axes.transAxes.inverted()
+            xaxes, yaxes = trans.transform((x, y))
+        except ValueError:
+            return False, {}
+        (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
+        inaxis = 0 <= xaxes <= 1 and (
+            b - self.pickradius < y < b or
+            t < y < t + self.pickradius)
+        return inaxis, {}
+
+    def _get_tick(self, major):
+        if major:
+            tick_kw = self._major_tick_kw
+        else:
+            tick_kw = self._minor_tick_kw
+        return XTick(self.axes, 0, '', major=major, **tick_kw)
+
+    def _get_label(self):
+        # x in axes coords, y in display coords (to be updated at draw
+        # time by _update_label_positions)
+        label = mtext.Text(x=0.5, y=0,
+                           fontproperties=font_manager.FontProperties(
+                               size=rcParams['axes.labelsize'],
+                               weight=rcParams['axes.labelweight']),
+                           color=rcParams['axes.labelcolor'],
+                           verticalalignment='top',
+                           horizontalalignment='center')
+
+        label.set_transform(mtransforms.blended_transform_factory(
+            self.axes.transAxes, mtransforms.IdentityTransform()))
+
+        self._set_artist_props(label)
+        self.label_position = 'bottom'
+        return label
+
+    def _get_offset_text(self):
+        # x in axes coords, y in display coords (to be updated at draw time)
+        offsetText = mtext.Text(x=1, y=0,
+                                fontproperties=font_manager.FontProperties(
+                                    size=rcParams['xtick.labelsize']),
+                                color=rcParams['xtick.color'],
+                                verticalalignment='top',
+                                horizontalalignment='right')
+        offsetText.set_transform(mtransforms.blended_transform_factory(
+            self.axes.transAxes, mtransforms.IdentityTransform())
+        )
+        self._set_artist_props(offsetText)
+        self.offset_text_position = 'bottom'
+        return offsetText
+
+    def set_label_position(self, position):
+        """
+        Set the label position (top or bottom)
+
+        Parameters
+        ----------
+        position : {'top', 'bottom'}
+        """
+        self.label.set_verticalalignment(cbook._check_getitem({
+            'top': 'baseline', 'bottom': 'top',
+        }, position=position))
+        self.label_position = position
+        self.stale = True
+
+    def _get_tick_boxes_siblings(self, renderer):
+        """
+        Get the bounding boxes for this `.axis` and its siblings
+        as set by `.Figure.align_xlabels` or  `.Figure.align_ylablels`.
+
+        By default it just gets bboxes for self.
+        """
+        bboxes = []
+        bboxes2 = []
+        # get the Grouper that keeps track of x-label groups for this figure
+        grp = self.figure._align_xlabel_grp
+        # if we want to align labels from other axes:
+        for nn, axx in enumerate(grp.get_siblings(self.axes)):
+            ticks_to_draw = axx.xaxis._update_ticks()
+            tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer)
+            bboxes.extend(tlb)
+            bboxes2.extend(tlb2)
+        return bboxes, bboxes2
+
+    def _update_label_position(self, renderer):
+        """
+        Update the label position based on the bounding box enclosing
+        all the ticklabels and axis spine
+        """
+        if not self._autolabelpos:
+            return
+
+        # get bounding boxes for this axis and any siblings
+        # that have been set by `fig.align_xlabels()`
+        bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
+
+        x, y = self.label.get_position()
+        if self.label_position == 'bottom':
+            try:
+                spine = self.axes.spines['bottom']
+                spinebbox = spine.get_transform().transform_path(
+                    spine.get_path()).get_extents()
+            except KeyError:
+                # use axes if spine doesn't exist
+                spinebbox = self.axes.bbox
+            bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
+            bottom = bbox.y0
+
+            self.label.set_position(
+                (x, bottom - self.labelpad * self.figure.dpi / 72)
+            )
+
+        else:
+            try:
+                spine = self.axes.spines['top']
+                spinebbox = spine.get_transform().transform_path(
+                    spine.get_path()).get_extents()
+            except KeyError:
+                # use axes if spine doesn't exist
+                spinebbox = self.axes.bbox
+            bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
+            top = bbox.y1
+
+            self.label.set_position(
+                (x, top + self.labelpad * self.figure.dpi / 72)
+            )
+
+    def _update_offset_text_position(self, bboxes, bboxes2):
+        """
+        Update the offset_text position based on the sequence of bounding
+        boxes of all the ticklabels
+        """
+        x, y = self.offsetText.get_position()
+        if not len(bboxes):
+            bottom = self.axes.bbox.ymin
+        else:
+            bbox = mtransforms.Bbox.union(bboxes)
+            bottom = bbox.y0
+        self.offsetText.set_position(
+            (x, bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72)
+        )
+
+    def get_text_heights(self, renderer):
+        """
+        Returns the amount of space one should reserve for text
+        above and below the axes.  Returns a tuple (above, below)
+        """
+        bbox, bbox2 = self.get_ticklabel_extents(renderer)
+        # MGDTODO: Need a better way to get the pad
+        padPixels = self.majorTicks[0].get_pad_pixels()
+
+        above = 0.0
+        if bbox2.height:
+            above += bbox2.height + padPixels
+        below = 0.0
+        if bbox.height:
+            below += bbox.height + padPixels
+
+        if self.get_label_position() == 'top':
+            above += self.label.get_window_extent(renderer).height + padPixels
+        else:
+            below += self.label.get_window_extent(renderer).height + padPixels
+        return above, below
+
+    def set_ticks_position(self, position):
+        """
+        Set the ticks position (top, bottom, both, default or none)
+        both sets the ticks to appear on both positions, but does not
+        change the tick labels.  'default' resets the tick positions to
+        the default: ticks on both positions, labels at bottom.  'none'
+        can be used if you don't want any ticks. 'none' and 'both'
+        affect only the ticks, not the labels.
+
+        Parameters
+        ----------
+        position : {'top', 'bottom', 'both', 'default', 'none'}
+        """
+        if position == 'top':
+            self.set_tick_params(which='both', top=True, labeltop=True,
+                                 bottom=False, labelbottom=False)
+        elif position == 'bottom':
+            self.set_tick_params(which='both', top=False, labeltop=False,
+                                 bottom=True, labelbottom=True)
+        elif position == 'both':
+            self.set_tick_params(which='both', top=True,
+                                 bottom=True)
+        elif position == 'none':
+            self.set_tick_params(which='both', top=False,
+                                 bottom=False)
+        elif position == 'default':
+            self.set_tick_params(which='both', top=True, labeltop=False,
+                                 bottom=True, labelbottom=True)
+        else:
+            raise ValueError("invalid position: %s" % position)
+        self.stale = True
+
+    def tick_top(self):
+        """
+        Move ticks and ticklabels (if present) to the top of the axes.
+        """
+        label = True
+        if 'label1On' in self._major_tick_kw:
+            label = (self._major_tick_kw['label1On']
+                     or self._major_tick_kw['label2On'])
+        self.set_ticks_position('top')
+        # If labels were turned off before this was called, leave them off.
+        self.set_tick_params(which='both', labeltop=label)
+
+    def tick_bottom(self):
+        """
+        Move ticks and ticklabels (if present) to the bottom of the axes.
+        """
+        label = True
+        if 'label1On' in self._major_tick_kw:
+            label = (self._major_tick_kw['label1On']
+                     or self._major_tick_kw['label2On'])
+        self.set_ticks_position('bottom')
+        # If labels were turned off before this was called, leave them off.
+        self.set_tick_params(which='both', labelbottom=label)
+
+    def get_ticks_position(self):
+        """
+        Return the ticks position ("top", "bottom", "default", or "unknown").
+        """
+        return {1: "bottom", 2: "top",
+                "default": "default", "unknown": "unknown"}[
+                    self._get_ticks_position()]
+
+    get_view_interval, set_view_interval = _make_getset_interval(
+        "view", "viewLim", "intervalx")
+    get_data_interval, set_data_interval = _make_getset_interval(
+        "data", "dataLim", "intervalx")
+
+    def get_minpos(self):
+        return self.axes.dataLim.minposx
+
+    def set_inverted(self, inverted):
+        # docstring inherited
+        a, b = self.get_view_interval()
+        # cast to bool to avoid bad interaction between python 3.8 and np.bool_
+        self.axes.set_xlim(sorted((a, b), reverse=bool(inverted)), auto=None)
+
+    def set_default_intervals(self):
+        # docstring inherited
+        xmin, xmax = 0., 1.
+        dataMutated = self.axes.dataLim.mutatedx()
+        viewMutated = self.axes.viewLim.mutatedx()
+        if not dataMutated or not viewMutated:
+            if self.converter is not None:
+                info = self.converter.axisinfo(self.units, self)
+                if info.default_limits is not None:
+                    valmin, valmax = info.default_limits
+                    xmin = self.converter.convert(valmin, self.units, self)
+                    xmax = self.converter.convert(valmax, self.units, self)
+            if not dataMutated:
+                self.axes.dataLim.intervalx = xmin, xmax
+            if not viewMutated:
+                self.axes.viewLim.intervalx = xmin, xmax
+        self.stale = True
+
+    def get_tick_space(self):
+        ends = self.axes.transAxes.transform([[0, 0], [1, 0]])
+        length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72
+        tick = self._get_tick(True)
+        # There is a heuristic here that the aspect ratio of tick text
+        # is no more than 3:1
+        size = tick.label1.get_size() * 3
+        if size > 0:
+            return int(np.floor(length / size))
+        else:
+            return 2**31 - 1
+
+
+class YAxis(Axis):
+    __name__ = 'yaxis'
+    axis_name = 'y'  #: Read-only name identifying the axis.
+
+    def contains(self, mouseevent):
+        """Test whether the mouse event occurred in the y axis.
+
+        Returns *True* | *False*
+        """
+        inside, info = self._default_contains(mouseevent)
+        if inside is not None:
+            return inside, info
+
+        x, y = mouseevent.x, mouseevent.y
+        try:
+            trans = self.axes.transAxes.inverted()
+            xaxes, yaxes = trans.transform((x, y))
+        except ValueError:
+            return False, {}
+        (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
+        inaxis = 0 <= yaxes <= 1 and (
+            l - self.pickradius < x < l or
+            r < x < r + self.pickradius)
+        return inaxis, {}
+
+    def _get_tick(self, major):
+        if major:
+            tick_kw = self._major_tick_kw
+        else:
+            tick_kw = self._minor_tick_kw
+        return YTick(self.axes, 0, '', major=major, **tick_kw)
+
+    def _get_label(self):
+        # x in display coords (updated by _update_label_position)
+        # y in axes coords
+        label = mtext.Text(x=0, y=0.5,
+                           # todo: get the label position
+                           fontproperties=font_manager.FontProperties(
+                               size=rcParams['axes.labelsize'],
+                               weight=rcParams['axes.labelweight']),
+                           color=rcParams['axes.labelcolor'],
+                           verticalalignment='bottom',
+                           horizontalalignment='center',
+                           rotation='vertical',
+                           rotation_mode='anchor')
+        label.set_transform(mtransforms.blended_transform_factory(
+            mtransforms.IdentityTransform(), self.axes.transAxes))
+
+        self._set_artist_props(label)
+        self.label_position = 'left'
+        return label
+
+    def _get_offset_text(self):
+        # x in display coords, y in axes coords (to be updated at draw time)
+        offsetText = mtext.Text(x=0, y=0.5,
+                                fontproperties=font_manager.FontProperties(
+                                    size=rcParams['ytick.labelsize']
+                                ),
+                                color=rcParams['ytick.color'],
+                                verticalalignment='baseline',
+                                horizontalalignment='left')
+        offsetText.set_transform(mtransforms.blended_transform_factory(
+            self.axes.transAxes, mtransforms.IdentityTransform())
+        )
+        self._set_artist_props(offsetText)
+        self.offset_text_position = 'left'
+        return offsetText
+
+    def set_label_position(self, position):
+        """
+        Set the label position (left or right)
+
+        Parameters
+        ----------
+        position : {'left', 'right'}
+        """
+        self.label.set_rotation_mode('anchor')
+        self.label.set_horizontalalignment('center')
+        self.label.set_verticalalignment(cbook._check_getitem({
+            'left': 'bottom', 'right': 'top',
+        }, position=position))
+        self.label_position = position
+        self.stale = True
+
+    def _get_tick_boxes_siblings(self, renderer):
+        """
+        Get the bounding boxes for this `.axis` and its siblings
+        as set by `.Figure.align_xlabels` or  `.Figure.align_ylablels`.
+
+        By default it just gets bboxes for self.
+        """
+        bboxes = []
+        bboxes2 = []
+        # get the Grouper that keeps track of y-label groups for this figure
+        grp = self.figure._align_ylabel_grp
+        # if we want to align labels from other axes:
+        for axx in grp.get_siblings(self.axes):
+            ticks_to_draw = axx.yaxis._update_ticks()
+            tlb, tlb2 = axx.yaxis._get_tick_bboxes(ticks_to_draw, renderer)
+            bboxes.extend(tlb)
+            bboxes2.extend(tlb2)
+        return bboxes, bboxes2
+
+    def _update_label_position(self, renderer):
+        """
+        Update the label position based on the bounding box enclosing
+        all the ticklabels and axis spine
+        """
+        if not self._autolabelpos:
+            return
+
+        # get bounding boxes for this axis and any siblings
+        # that have been set by `fig.align_ylabels()`
+        bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
+
+        x, y = self.label.get_position()
+        if self.label_position == 'left':
+            try:
+                spine = self.axes.spines['left']
+                spinebbox = spine.get_transform().transform_path(
+                    spine.get_path()).get_extents()
+            except KeyError:
+                # use axes if spine doesn't exist
+                spinebbox = self.axes.bbox
+            bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
+            left = bbox.x0
+            self.label.set_position(
+                (left - self.labelpad * self.figure.dpi / 72, y)
+            )
+
+        else:
+            try:
+                spine = self.axes.spines['right']
+                spinebbox = spine.get_transform().transform_path(
+                    spine.get_path()).get_extents()
+            except KeyError:
+                # use axes if spine doesn't exist
+                spinebbox = self.axes.bbox
+            bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
+            right = bbox.x1
+
+            self.label.set_position(
+                (right + self.labelpad * self.figure.dpi / 72, y)
+            )
+
+    def _update_offset_text_position(self, bboxes, bboxes2):
+        """
+        Update the offset_text position based on the sequence of bounding
+        boxes of all the ticklabels
+        """
+        x, y = self.offsetText.get_position()
+        top = self.axes.bbox.ymax
+        self.offsetText.set_position(
+            (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72)
+        )
+
+    def set_offset_position(self, position):
+        """
+        Parameters
+        ----------
+        position : {'left', 'right'}
+        """
+        x, y = self.offsetText.get_position()
+        x = cbook._check_getitem({'left': 0, 'right': 1}, position=position)
+
+        self.offsetText.set_ha(position)
+        self.offsetText.set_position((x, y))
+        self.stale = True
+
+    def get_text_widths(self, renderer):
+        bbox, bbox2 = self.get_ticklabel_extents(renderer)
+        # MGDTODO: Need a better way to get the pad
+        padPixels = self.majorTicks[0].get_pad_pixels()
+
+        left = 0.0
+        if bbox.width:
+            left += bbox.width + padPixels
+        right = 0.0
+        if bbox2.width:
+            right += bbox2.width + padPixels
+
+        if self.get_label_position() == 'left':
+            left += self.label.get_window_extent(renderer).width + padPixels
+        else:
+            right += self.label.get_window_extent(renderer).width + padPixels
+        return left, right
+
+    def set_ticks_position(self, position):
+        """
+        Set the ticks position (left, right, both, default or none)
+        'both' sets the ticks to appear on both positions, but does not
+        change the tick labels.  'default' resets the tick positions to
+        the default: ticks on both positions, labels at left.  'none'
+        can be used if you don't want any ticks. 'none' and 'both'
+        affect only the ticks, not the labels.
+
+        Parameters
+        ----------
+        position : {'left', 'right', 'both', 'default', 'none'}
+        """
+        if position == 'right':
+            self.set_tick_params(which='both', right=True, labelright=True,
+                                 left=False, labelleft=False)
+            self.set_offset_position(position)
+        elif position == 'left':
+            self.set_tick_params(which='both', right=False, labelright=False,
+                                 left=True, labelleft=True)
+            self.set_offset_position(position)
+        elif position == 'both':
+            self.set_tick_params(which='both', right=True,
+                                 left=True)
+        elif position == 'none':
+            self.set_tick_params(which='both', right=False,
+                                 left=False)
+        elif position == 'default':
+            self.set_tick_params(which='both', right=True, labelright=False,
+                                 left=True, labelleft=True)
+        else:
+            raise ValueError("invalid position: %s" % position)
+        self.stale = True
+
+    def tick_right(self):
+        """
+        Move ticks and ticklabels (if present) to the right of the axes.
+        """
+        label = True
+        if 'label1On' in self._major_tick_kw:
+            label = (self._major_tick_kw['label1On']
+                     or self._major_tick_kw['label2On'])
+        self.set_ticks_position('right')
+        # if labels were turned off before this was called
+        # leave them off
+        self.set_tick_params(which='both', labelright=label)
+
+    def tick_left(self):
+        """
+        Move ticks and ticklabels (if present) to the left of the axes.
+        """
+        label = True
+        if 'label1On' in self._major_tick_kw:
+            label = (self._major_tick_kw['label1On']
+                     or self._major_tick_kw['label2On'])
+        self.set_ticks_position('left')
+        # if labels were turned off before this was called
+        # leave them off
+        self.set_tick_params(which='both', labelleft=label)
+
+    def get_ticks_position(self):
+        """
+        Return the ticks position ("left", "right", "default", or "unknown").
+        """
+        return {1: "left", 2: "right",
+                "default": "default", "unknown": "unknown"}[
+                    self._get_ticks_position()]
+
+    get_view_interval, set_view_interval = _make_getset_interval(
+        "view", "viewLim", "intervaly")
+    get_data_interval, set_data_interval = _make_getset_interval(
+        "data", "dataLim", "intervaly")
+
+    def get_minpos(self):
+        return self.axes.dataLim.minposy
+
+    def set_inverted(self, inverted):
+        # docstring inherited
+        a, b = self.get_view_interval()
+        # cast to bool to avoid bad interaction between python 3.8 and np.bool_
+        self.axes.set_ylim(sorted((a, b), reverse=bool(inverted)), auto=None)
+
+    def set_default_intervals(self):
+        # docstring inherited
+        ymin, ymax = 0., 1.
+        dataMutated = self.axes.dataLim.mutatedy()
+        viewMutated = self.axes.viewLim.mutatedy()
+        if not dataMutated or not viewMutated:
+            if self.converter is not None:
+                info = self.converter.axisinfo(self.units, self)
+                if info.default_limits is not None:
+                    valmin, valmax = info.default_limits
+                    ymin = self.converter.convert(valmin, self.units, self)
+                    ymax = self.converter.convert(valmax, self.units, self)
+            if not dataMutated:
+                self.axes.dataLim.intervaly = ymin, ymax
+            if not viewMutated:
+                self.axes.viewLim.intervaly = ymin, ymax
+        self.stale = True
+
+    def get_tick_space(self):
+        ends = self.axes.transAxes.transform([[0, 0], [0, 1]])
+        length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72
+        tick = self._get_tick(True)
+        # Having a spacing of at least 2 just looks good.
+        size = tick.label1.get_size() * 2.0
+        if size > 0:
+            return int(np.floor(length / size))
+        else:
+            return 2**31 - 1

+ 3421 - 0
venv/lib/python3.8/site-packages/matplotlib/backend_bases.py

@@ -0,0 +1,3421 @@
+"""
+Abstract base classes define the primitives that renderers and
+graphics contexts must implement to serve as a matplotlib backend
+
+:class:`RendererBase`
+    An abstract base class to handle drawing/rendering operations.
+
+:class:`FigureCanvasBase`
+    The abstraction layer that separates the
+    :class:`matplotlib.figure.Figure` from the backend specific
+    details like a user interface drawing area
+
+:class:`GraphicsContextBase`
+    An abstract base class that provides color, line styles, etc...
+
+:class:`Event`
+    The base class for all of the matplotlib event
+    handling.  Derived classes such as :class:`KeyEvent` and
+    :class:`MouseEvent` store the meta data like keys and buttons
+    pressed, x and y locations in pixel and
+    :class:`~matplotlib.axes.Axes` coordinates.
+
+:class:`ShowBase`
+    The base class for the Show class of each interactive backend;
+    the 'show' callable is then set to Show.__call__, inherited from
+    ShowBase.
+
+:class:`ToolContainerBase`
+     The base class for the Toolbar class of each interactive backend.
+
+:class:`StatusbarBase`
+    The base class for the messaging area.
+"""
+
+from contextlib import contextmanager
+from enum import IntEnum
+import functools
+import importlib
+import io
+import logging
+import os
+import sys
+import time
+from weakref import WeakKeyDictionary
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import (
+    backend_tools as tools, cbook, colors, textpath, tight_bbox, transforms,
+    widgets, get_backend, is_interactive, rcParams)
+from matplotlib._pylab_helpers import Gcf
+from matplotlib.transforms import Affine2D
+from matplotlib.path import Path
+
+try:
+    from PIL import __version__ as PILLOW_VERSION
+    from distutils.version import LooseVersion
+    if LooseVersion(PILLOW_VERSION) >= "3.4":
+        _has_pil = True
+    else:
+        _has_pil = False
+    del PILLOW_VERSION
+except ImportError:
+    _has_pil = False
+
+_log = logging.getLogger(__name__)
+
+_default_filetypes = {
+    'ps': 'Postscript',
+    'eps': 'Encapsulated Postscript',
+    'pdf': 'Portable Document Format',
+    'pgf': 'PGF code for LaTeX',
+    'png': 'Portable Network Graphics',
+    'raw': 'Raw RGBA bitmap',
+    'rgba': 'Raw RGBA bitmap',
+    'svg': 'Scalable Vector Graphics',
+    'svgz': 'Scalable Vector Graphics'
+}
+
+
+_default_backends = {
+    'ps': 'matplotlib.backends.backend_ps',
+    'eps': 'matplotlib.backends.backend_ps',
+    'pdf': 'matplotlib.backends.backend_pdf',
+    'pgf': 'matplotlib.backends.backend_pgf',
+    'png': 'matplotlib.backends.backend_agg',
+    'raw': 'matplotlib.backends.backend_agg',
+    'rgba': 'matplotlib.backends.backend_agg',
+    'svg': 'matplotlib.backends.backend_svg',
+    'svgz': 'matplotlib.backends.backend_svg',
+}
+
+
+def register_backend(format, backend, description=None):
+    """
+    Register a backend for saving to a given file format.
+
+    Parameters
+    ----------
+    format : str
+        File extension
+
+    backend : module string or canvas class
+        Backend for handling file output
+
+    description : str, default: ""
+        Description of the file type.
+    """
+    if description is None:
+        description = ''
+    _default_backends[format] = backend
+    _default_filetypes[format] = description
+
+
+def get_registered_canvas_class(format):
+    """
+    Return the registered default canvas for given file format.
+    Handles deferred import of required backend.
+    """
+    if format not in _default_backends:
+        return None
+    backend_class = _default_backends[format]
+    if isinstance(backend_class, str):
+        backend_class = importlib.import_module(backend_class).FigureCanvas
+        _default_backends[format] = backend_class
+    return backend_class
+
+
+class RendererBase:
+    """An abstract base class to handle drawing/rendering operations.
+
+    The following methods must be implemented in the backend for full
+    functionality (though just implementing :meth:`draw_path` alone would
+    give a highly capable backend):
+
+    * :meth:`draw_path`
+    * :meth:`draw_image`
+    * :meth:`draw_gouraud_triangle`
+
+    The following methods *should* be implemented in the backend for
+    optimization reasons:
+
+    * :meth:`draw_text`
+    * :meth:`draw_markers`
+    * :meth:`draw_path_collection`
+    * :meth:`draw_quad_mesh`
+    """
+
+    def __init__(self):
+        self._texmanager = None
+        self._text2path = textpath.TextToPath()
+
+    def open_group(self, s, gid=None):
+        """
+        Open a grouping element with label *s* and *gid* (if set) as id.
+
+        Only used by the SVG renderer.
+        """
+
+    def close_group(self, s):
+        """
+        Close a grouping element with label *s*.
+
+        Only used by the SVG renderer.
+        """
+
+    def draw_path(self, gc, path, transform, rgbFace=None):
+        """Draw a `~.path.Path` instance using the given affine transform."""
+        raise NotImplementedError
+
+    def draw_markers(self, gc, marker_path, marker_trans, path,
+                     trans, rgbFace=None):
+        """
+        Draw a marker at each of the vertices in path.
+
+        This includes all vertices, including control points on curves.
+        To avoid that behavior, those vertices should be removed before
+        calling this function.
+
+        This provides a fallback implementation of draw_markers that
+        makes multiple calls to :meth:`draw_path`.  Some backends may
+        want to override this method in order to draw the marker only
+        once and reuse it multiple times.
+
+        Parameters
+        ----------
+        gc : `GraphicsContextBase`
+            The graphics context.
+
+        marker_trans : `matplotlib.transforms.Transform`
+            An affine transform applied to the marker.
+
+        trans : `matplotlib.transforms.Transform`
+            An affine transform applied to the path.
+
+        """
+        for vertices, codes in path.iter_segments(trans, simplify=False):
+            if len(vertices):
+                x, y = vertices[-2:]
+                self.draw_path(gc, marker_path,
+                               marker_trans +
+                               transforms.Affine2D().translate(x, y),
+                               rgbFace)
+
+    def draw_path_collection(self, gc, master_transform, paths, all_transforms,
+                             offsets, offsetTrans, facecolors, edgecolors,
+                             linewidths, linestyles, antialiaseds, urls,
+                             offset_position):
+        """
+        Draw a collection of paths selecting drawing properties from
+        the lists *facecolors*, *edgecolors*, *linewidths*,
+        *linestyles* and *antialiaseds*. *offsets* is a list of
+        offsets to apply to each of the paths.  The offsets in
+        *offsets* are first transformed by *offsetTrans* before being
+        applied.  *offset_position* may be either "screen" or "data"
+        depending on the space that the offsets are in.
+
+        This provides a fallback implementation of
+        :meth:`draw_path_collection` that makes multiple calls to
+        :meth:`draw_path`.  Some backends may want to override this in
+        order to render each set of path data only once, and then
+        reference that path multiple times with the different offsets,
+        colors, styles etc.  The generator methods
+        :meth:`_iter_collection_raw_paths` and
+        :meth:`_iter_collection` are provided to help with (and
+        standardize) the implementation across backends.  It is highly
+        recommended to use those generators, so that changes to the
+        behavior of :meth:`draw_path_collection` can be made globally.
+        """
+        path_ids = [
+            (path, transforms.Affine2D(transform))
+            for path, transform in self._iter_collection_raw_paths(
+                    master_transform, paths, all_transforms)]
+
+        for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
+                gc, master_transform, all_transforms, path_ids, offsets,
+                offsetTrans, facecolors, edgecolors, linewidths, linestyles,
+                antialiaseds, urls, offset_position):
+            path, transform = path_id
+            transform = transforms.Affine2D(
+                            transform.get_matrix()).translate(xo, yo)
+            self.draw_path(gc0, path, transform, rgbFace)
+
+    def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
+                       coordinates, offsets, offsetTrans, facecolors,
+                       antialiased, edgecolors):
+        """
+        This provides a fallback implementation of
+        :meth:`draw_quad_mesh` that generates paths and then calls
+        :meth:`draw_path_collection`.
+        """
+
+        from matplotlib.collections import QuadMesh
+        paths = QuadMesh.convert_mesh_to_paths(
+            meshWidth, meshHeight, coordinates)
+
+        if edgecolors is None:
+            edgecolors = facecolors
+        linewidths = np.array([gc.get_linewidth()], float)
+
+        return self.draw_path_collection(
+            gc, master_transform, paths, [], offsets, offsetTrans, facecolors,
+            edgecolors, linewidths, [], [antialiased], [None], 'screen')
+
+    def draw_gouraud_triangle(self, gc, points, colors, transform):
+        """
+        Draw a Gouraud-shaded triangle.
+
+        Parameters
+        ----------
+        points : array-like, shape=(3, 2)
+            Array of (x, y) points for the triangle.
+
+        colors : array-like, shape=(3, 4)
+            RGBA colors for each point of the triangle.
+
+        transform : `matplotlib.transforms.Transform`
+            An affine transform to apply to the points.
+
+        """
+        raise NotImplementedError
+
+    def draw_gouraud_triangles(self, gc, triangles_array, colors_array,
+                               transform):
+        """
+        Draw a series of Gouraud triangles.
+
+        Parameters
+        ----------
+        points : array-like, shape=(N, 3, 2)
+            Array of *N* (x, y) points for the triangles.
+
+        colors : array-like, shape=(N, 3, 4)
+            Array of *N* RGBA colors for each point of the triangles.
+
+        transform : `matplotlib.transforms.Transform`
+            An affine transform to apply to the points.
+        """
+        transform = transform.frozen()
+        for tri, col in zip(triangles_array, colors_array):
+            self.draw_gouraud_triangle(gc, tri, col, transform)
+
+    def _iter_collection_raw_paths(self, master_transform, paths,
+                                   all_transforms):
+        """
+        This is a helper method (along with :meth:`_iter_collection`) to make
+        it easier to write a space-efficient :meth:`draw_path_collection`
+        implementation in a backend.
+
+        This method yields all of the base path/transform
+        combinations, given a master transform, a list of paths and
+        list of transforms.
+
+        The arguments should be exactly what is passed in to
+        :meth:`draw_path_collection`.
+
+        The backend should take each yielded path and transform and
+        create an object that can be referenced (reused) later.
+        """
+        Npaths = len(paths)
+        Ntransforms = len(all_transforms)
+        N = max(Npaths, Ntransforms)
+
+        if Npaths == 0:
+            return
+
+        transform = transforms.IdentityTransform()
+        for i in range(N):
+            path = paths[i % Npaths]
+            if Ntransforms:
+                transform = Affine2D(all_transforms[i % Ntransforms])
+            yield path, transform + master_transform
+
+    def _iter_collection_uses_per_path(self, paths, all_transforms,
+                                       offsets, facecolors, edgecolors):
+        """
+        Compute how many times each raw path object returned by
+        _iter_collection_raw_paths would be used when calling
+        _iter_collection. This is intended for the backend to decide
+        on the tradeoff between using the paths in-line and storing
+        them once and reusing. Rounds up in case the number of uses
+        is not the same for every path.
+        """
+        Npaths = len(paths)
+        if Npaths == 0 or len(facecolors) == len(edgecolors) == 0:
+            return 0
+        Npath_ids = max(Npaths, len(all_transforms))
+        N = max(Npath_ids, len(offsets))
+        return (N + Npath_ids - 1) // Npath_ids
+
+    def _iter_collection(self, gc, master_transform, all_transforms,
+                         path_ids, offsets, offsetTrans, facecolors,
+                         edgecolors, linewidths, linestyles,
+                         antialiaseds, urls, offset_position):
+        """
+        This is a helper method (along with
+        :meth:`_iter_collection_raw_paths`) to make it easier to write
+        a space-efficient :meth:`draw_path_collection` implementation in a
+        backend.
+
+        This method yields all of the path, offset and graphics
+        context combinations to draw the path collection.  The caller
+        should already have looped over the results of
+        :meth:`_iter_collection_raw_paths` to draw this collection.
+
+        The arguments should be the same as that passed into
+        :meth:`draw_path_collection`, with the exception of
+        *path_ids*, which is a list of arbitrary objects that the
+        backend will use to reference one of the paths created in the
+        :meth:`_iter_collection_raw_paths` stage.
+
+        Each yielded result is of the form::
+
+           xo, yo, path_id, gc, rgbFace
+
+        where *xo*, *yo* is an offset; *path_id* is one of the elements of
+        *path_ids*; *gc* is a graphics context and *rgbFace* is a color to
+        use for filling the path.
+        """
+        Ntransforms = len(all_transforms)
+        Npaths = len(path_ids)
+        Noffsets = len(offsets)
+        N = max(Npaths, Noffsets)
+        Nfacecolors = len(facecolors)
+        Nedgecolors = len(edgecolors)
+        Nlinewidths = len(linewidths)
+        Nlinestyles = len(linestyles)
+        Naa = len(antialiaseds)
+        Nurls = len(urls)
+
+        if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0:
+            return
+        if Noffsets:
+            toffsets = offsetTrans.transform(offsets)
+
+        gc0 = self.new_gc()
+        gc0.copy_properties(gc)
+
+        if Nfacecolors == 0:
+            rgbFace = None
+
+        if Nedgecolors == 0:
+            gc0.set_linewidth(0.0)
+
+        xo, yo = 0, 0
+        for i in range(N):
+            path_id = path_ids[i % Npaths]
+            if Noffsets:
+                xo, yo = toffsets[i % Noffsets]
+                if offset_position == 'data':
+                    if Ntransforms:
+                        transform = (
+                            Affine2D(all_transforms[i % Ntransforms]) +
+                            master_transform)
+                    else:
+                        transform = master_transform
+                    (xo, yo), (xp, yp) = transform.transform(
+                        [(xo, yo), (0, 0)])
+                    xo = -(xp - xo)
+                    yo = -(yp - yo)
+            if not (np.isfinite(xo) and np.isfinite(yo)):
+                continue
+            if Nfacecolors:
+                rgbFace = facecolors[i % Nfacecolors]
+            if Nedgecolors:
+                if Nlinewidths:
+                    gc0.set_linewidth(linewidths[i % Nlinewidths])
+                if Nlinestyles:
+                    gc0.set_dashes(*linestyles[i % Nlinestyles])
+                fg = edgecolors[i % Nedgecolors]
+                if len(fg) == 4:
+                    if fg[3] == 0.0:
+                        gc0.set_linewidth(0)
+                    else:
+                        gc0.set_foreground(fg)
+                else:
+                    gc0.set_foreground(fg)
+            if rgbFace is not None and len(rgbFace) == 4:
+                if rgbFace[3] == 0:
+                    rgbFace = None
+            gc0.set_antialiased(antialiaseds[i % Naa])
+            if Nurls:
+                gc0.set_url(urls[i % Nurls])
+
+            yield xo, yo, path_id, gc0, rgbFace
+        gc0.restore()
+
+    def get_image_magnification(self):
+        """
+        Get the factor by which to magnify images passed to :meth:`draw_image`.
+        Allows a backend to have images at a different resolution to other
+        artists.
+        """
+        return 1.0
+
+    def draw_image(self, gc, x, y, im, transform=None):
+        """
+        Draw an RGBA image.
+
+        Parameters
+        ----------
+        gc : `GraphicsContextBase`
+            A graphics context with clipping information.
+
+        x : scalar
+            The distance in physical units (i.e., dots or pixels) from the left
+            hand side of the canvas.
+
+        y : scalar
+            The distance in physical units (i.e., dots or pixels) from the
+            bottom side of the canvas.
+
+        im : array-like, shape=(N, M, 4), dtype=np.uint8
+            An array of RGBA pixels.
+
+        transform : `matplotlib.transforms.Affine2DBase`
+            If and only if the concrete backend is written such that
+            :meth:`option_scale_image` returns ``True``, an affine
+            transformation *may* be passed to :meth:`draw_image`. It takes the
+            form of a :class:`~matplotlib.transforms.Affine2DBase` instance.
+            The translation vector of the transformation is given in physical
+            units (i.e., dots or pixels). Note that the transformation does not
+            override *x* and *y*, and has to be applied *before* translating
+            the result by *x* and *y* (this can be accomplished by adding *x*
+            and *y* to the translation vector defined by *transform*).
+        """
+        raise NotImplementedError
+
+    def option_image_nocomposite(self):
+        """
+        Return whether image composition by Matplotlib should be skipped.
+
+        Raster backends should usually return False (letting the C-level
+        rasterizer take care of image composition); vector backends should
+        usually return ``not rcParams["image.composite_image"]``.
+        """
+        return False
+
+    def option_scale_image(self):
+        """
+        Return whether arbitrary affine transformations in :meth:`draw_image`
+        are supported (True for most vector backends).
+        """
+        return False
+
+    def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
+        """
+        """
+        self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX")
+
+    def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
+        """
+        Draw the text instance.
+
+        Parameters
+        ----------
+        gc : `GraphicsContextBase`
+            The graphics context.
+        x : float
+            The x location of the text in display coords.
+        y : float
+            The y location of the text baseline in display coords.
+        s : str
+            The text string.
+        prop : `matplotlib.font_manager.FontProperties`
+            The font properties.
+        angle : float
+            The rotation angle in degrees anti-clockwise.
+        mtext : `matplotlib.text.Text`
+            The original text object to be rendered.
+
+        Notes
+        -----
+        **Note for backend implementers:**
+
+        When you are trying to determine if you have gotten your bounding box
+        right (which is what enables the text layout/alignment to work
+        properly), it helps to change the line in text.py::
+
+            if 0: bbox_artist(self, renderer)
+
+        to if 1, and then the actual bounding box will be plotted along with
+        your text.
+        """
+
+        self._draw_text_as_path(gc, x, y, s, prop, angle, ismath)
+
+    def _get_text_path_transform(self, x, y, s, prop, angle, ismath):
+        """
+        Return the text path and transform.
+
+        Parameters
+        ----------
+        prop : `matplotlib.font_manager.FontProperties`
+            The font property.
+        s : str
+            The text to be converted.
+        ismath : bool or "TeX"
+            If True, use mathtext parser. If "TeX", use *usetex* mode.
+        """
+
+        text2path = self._text2path
+        fontsize = self.points_to_pixels(prop.get_size_in_points())
+        verts, codes = text2path.get_text_path(prop, s, ismath=ismath)
+
+        path = Path(verts, codes)
+        angle = np.deg2rad(angle)
+        if self.flipy():
+            width, height = self.get_canvas_width_height()
+            transform = (Affine2D()
+                         .scale(fontsize / text2path.FONT_SCALE)
+                         .rotate(angle)
+                         .translate(x, height - y))
+        else:
+            transform = (Affine2D()
+                         .scale(fontsize / text2path.FONT_SCALE)
+                         .rotate(angle)
+                         .translate(x, y))
+
+        return path, transform
+
+    def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
+        """
+        Draw the text by converting them to paths using textpath module.
+
+        Parameters
+        ----------
+        prop : `matplotlib.font_manager.FontProperties`
+            The font property.
+        s : str
+            The text to be converted.
+        usetex : bool
+            Whether to use matplotlib usetex mode.
+        ismath : bool or "TeX"
+            If True, use mathtext parser. If "TeX", use *usetex* mode.
+        """
+        path, transform = self._get_text_path_transform(
+            x, y, s, prop, angle, ismath)
+        color = gc.get_rgb()
+        gc.set_linewidth(0.0)
+        self.draw_path(gc, path, transform, rgbFace=color)
+
+    def get_text_width_height_descent(self, s, prop, ismath):
+        """
+        Get the width, height, and descent (offset from the bottom
+        to the baseline), in display coords, of the string *s* with
+        :class:`~matplotlib.font_manager.FontProperties` *prop*
+        """
+        if ismath == 'TeX':
+            # todo: handle props
+            texmanager = self._text2path.get_texmanager()
+            fontsize = prop.get_size_in_points()
+            w, h, d = texmanager.get_text_width_height_descent(
+                s, fontsize, renderer=self)
+            return w, h, d
+
+        dpi = self.points_to_pixels(72)
+        if ismath:
+            dims = self._text2path.mathtext_parser.parse(s, dpi, prop)
+            return dims[0:3]  # return width, height, descent
+
+        flags = self._text2path._get_hinting_flag()
+        font = self._text2path._get_font(prop)
+        size = prop.get_size_in_points()
+        font.set_size(size, dpi)
+        # the width and height of unrotated string
+        font.set_text(s, 0.0, flags=flags)
+        w, h = font.get_width_height()
+        d = font.get_descent()
+        w /= 64.0  # convert from subpixels
+        h /= 64.0
+        d /= 64.0
+        return w, h, d
+
+    def flipy(self):
+        """
+        Return whether y values increase from top to bottom.
+
+        Note that this only affects drawing of texts and images.
+        """
+        return True
+
+    def get_canvas_width_height(self):
+        """Return the canvas width and height in display coords."""
+        return 1, 1
+
+    def get_texmanager(self):
+        """Return the `.TexManager` instance."""
+        if self._texmanager is None:
+            from matplotlib.texmanager import TexManager
+            self._texmanager = TexManager()
+        return self._texmanager
+
+    def new_gc(self):
+        """Return an instance of a `GraphicsContextBase`."""
+        return GraphicsContextBase()
+
+    def points_to_pixels(self, points):
+        """
+        Convert points to display units.
+
+        You need to override this function (unless your backend
+        doesn't have a dpi, e.g., postscript or svg).  Some imaging
+        systems assume some value for pixels per inch::
+
+            points to pixels = points * pixels_per_inch/72 * dpi/72
+
+        Parameters
+        ----------
+        points : float or array-like
+            a float or a numpy array of float
+
+        Returns
+        -------
+        Points converted to pixels
+        """
+        return points
+
+    @cbook.deprecated("3.1", alternative="cbook.strip_math")
+    def strip_math(self, s):
+        return cbook.strip_math(s)
+
+    def start_rasterizing(self):
+        """
+        Switch to the raster renderer.
+
+        Used by `MixedModeRenderer`.
+        """
+
+    def stop_rasterizing(self):
+        """
+        Switch back to the vector renderer and draw the contents of the raster
+        renderer as an image on the vector renderer.
+
+        Used by `MixedModeRenderer`.
+        """
+
+    def start_filter(self):
+        """
+        Switch to a temporary renderer for image filtering effects.
+
+        Currently only supported by the agg renderer.
+        """
+
+    def stop_filter(self, filter_func):
+        """
+        Switch back to the original renderer.  The contents of the temporary
+        renderer is processed with the *filter_func* and is drawn on the
+        original renderer as an image.
+
+        Currently only supported by the agg renderer.
+        """
+
+
+class GraphicsContextBase:
+    """An abstract base class that provides color, line styles, etc."""
+
+    def __init__(self):
+        self._alpha = 1.0
+        self._forced_alpha = False  # if True, _alpha overrides A from RGBA
+        self._antialiased = 1  # use 0, 1 not True, False for extension code
+        self._capstyle = 'butt'
+        self._cliprect = None
+        self._clippath = None
+        self._dashes = None, None
+        self._joinstyle = 'round'
+        self._linestyle = 'solid'
+        self._linewidth = 1
+        self._rgb = (0.0, 0.0, 0.0, 1.0)
+        self._hatch = None
+        self._hatch_color = colors.to_rgba(rcParams['hatch.color'])
+        self._hatch_linewidth = rcParams['hatch.linewidth']
+        self._url = None
+        self._gid = None
+        self._snap = None
+        self._sketch = None
+
+    def copy_properties(self, gc):
+        'Copy properties from gc to self'
+        self._alpha = gc._alpha
+        self._forced_alpha = gc._forced_alpha
+        self._antialiased = gc._antialiased
+        self._capstyle = gc._capstyle
+        self._cliprect = gc._cliprect
+        self._clippath = gc._clippath
+        self._dashes = gc._dashes
+        self._joinstyle = gc._joinstyle
+        self._linestyle = gc._linestyle
+        self._linewidth = gc._linewidth
+        self._rgb = gc._rgb
+        self._hatch = gc._hatch
+        self._hatch_color = gc._hatch_color
+        self._hatch_linewidth = gc._hatch_linewidth
+        self._url = gc._url
+        self._gid = gc._gid
+        self._snap = gc._snap
+        self._sketch = gc._sketch
+
+    def restore(self):
+        """
+        Restore the graphics context from the stack - needed only
+        for backends that save graphics contexts on a stack.
+        """
+
+    def get_alpha(self):
+        """
+        Return the alpha value used for blending - not supported on
+        all backends.
+        """
+        return self._alpha
+
+    def get_antialiased(self):
+        "Return whether the object should try to do antialiased rendering."
+        return self._antialiased
+
+    def get_capstyle(self):
+        """
+        Return the capstyle as a string in ('butt', 'round', 'projecting').
+        """
+        return self._capstyle
+
+    def get_clip_rectangle(self):
+        """
+        Return the clip rectangle as a `~matplotlib.transforms.Bbox` instance.
+        """
+        return self._cliprect
+
+    def get_clip_path(self):
+        """
+        Return the clip path in the form (path, transform), where path
+        is a :class:`~matplotlib.path.Path` instance, and transform is
+        an affine transform to apply to the path before clipping.
+        """
+        if self._clippath is not None:
+            return self._clippath.get_transformed_path_and_affine()
+        return None, None
+
+    def get_dashes(self):
+        """
+        Return the dash style as an (offset, dash-list) pair.
+
+        The dash list is a even-length list that gives the ink on, ink off in
+        points.  See p. 107 of to PostScript `blue book`_ for more info.
+
+        Default value is (None, None).
+
+        .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
+        """
+        return self._dashes
+
+    def get_forced_alpha(self):
+        """
+        Return whether the value given by get_alpha() should be used to
+        override any other alpha-channel values.
+        """
+        return self._forced_alpha
+
+    def get_joinstyle(self):
+        """Return the line join style as one of ('miter', 'round', 'bevel')."""
+        return self._joinstyle
+
+    def get_linewidth(self):
+        """Return the line width in points."""
+        return self._linewidth
+
+    def get_rgb(self):
+        """Return a tuple of three or four floats from 0-1."""
+        return self._rgb
+
+    def get_url(self):
+        """Return a url if one is set, None otherwise."""
+        return self._url
+
+    def get_gid(self):
+        """Return the object identifier if one is set, None otherwise."""
+        return self._gid
+
+    def get_snap(self):
+        """
+        Returns the snap setting, which can be:
+
+        * True: snap vertices to the nearest pixel center
+        * False: leave vertices as-is
+        * None: (auto) If the path contains only rectilinear line segments,
+          round to the nearest pixel center
+        """
+        return self._snap
+
+    def set_alpha(self, alpha):
+        """
+        Set the alpha value used for blending - not supported on all backends.
+
+        If ``alpha=None`` (the default), the alpha components of the
+        foreground and fill colors will be used to set their respective
+        transparencies (where applicable); otherwise, ``alpha`` will override
+        them.
+        """
+        if alpha is not None:
+            self._alpha = alpha
+            self._forced_alpha = True
+        else:
+            self._alpha = 1.0
+            self._forced_alpha = False
+        self.set_foreground(self._rgb, isRGBA=True)
+
+    def set_antialiased(self, b):
+        """Set whether object should be drawn with antialiased rendering."""
+        # Use ints to make life easier on extension code trying to read the gc.
+        self._antialiased = int(bool(b))
+
+    def set_capstyle(self, cs):
+        """Set the capstyle to be one of ('butt', 'round', 'projecting')."""
+        cbook._check_in_list(['butt', 'round', 'projecting'], cs=cs)
+        self._capstyle = cs
+
+    def set_clip_rectangle(self, rectangle):
+        """
+        Set the clip rectangle with sequence (left, bottom, width, height)
+        """
+        self._cliprect = rectangle
+
+    def set_clip_path(self, path):
+        """
+        Set the clip path and transformation.
+
+        Parameters
+        ----------
+        path : `~matplotlib.transforms.TransformedPath` or None
+        """
+        cbook._check_isinstance((transforms.TransformedPath, None), path=path)
+        self._clippath = path
+
+    def set_dashes(self, dash_offset, dash_list):
+        """
+        Set the dash style for the gc.
+
+        Parameters
+        ----------
+        dash_offset : float or None
+            The offset (usually 0).
+        dash_list : array-like or None
+            The on-off sequence as points.
+
+        Notes
+        -----
+        ``(None, None)`` specifies a solid line.
+
+        See p. 107 of to PostScript `blue book`_ for more info.
+
+        .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
+        """
+        if dash_list is not None:
+            dl = np.asarray(dash_list)
+            if np.any(dl < 0.0):
+                raise ValueError(
+                    "All values in the dash list must be positive")
+        self._dashes = dash_offset, dash_list
+
+    def set_foreground(self, fg, isRGBA=False):
+        """
+        Set the foreground color.
+
+        Parameters
+        ----------
+        fg : color
+        isRGBA : bool
+            If *fg* is known to be an ``(r, g, b, a)`` tuple, *isRGBA* can be
+            set to True to improve performance.
+        """
+        if self._forced_alpha and isRGBA:
+            self._rgb = fg[:3] + (self._alpha,)
+        elif self._forced_alpha:
+            self._rgb = colors.to_rgba(fg, self._alpha)
+        elif isRGBA:
+            self._rgb = fg
+        else:
+            self._rgb = colors.to_rgba(fg)
+
+    def set_joinstyle(self, js):
+        """Set the join style to be one of ('miter', 'round', 'bevel')."""
+        cbook._check_in_list(['miter', 'round', 'bevel'], js=js)
+        self._joinstyle = js
+
+    def set_linewidth(self, w):
+        """Set the linewidth in points."""
+        self._linewidth = float(w)
+
+    def set_url(self, url):
+        """Set the url for links in compatible backends."""
+        self._url = url
+
+    def set_gid(self, id):
+        """Set the id."""
+        self._gid = id
+
+    def set_snap(self, snap):
+        """
+        Set the snap setting which may be:
+
+        * True: snap vertices to the nearest pixel center
+        * False: leave vertices as-is
+        * None: (auto) If the path contains only rectilinear line segments,
+          round to the nearest pixel center
+        """
+        self._snap = snap
+
+    def set_hatch(self, hatch):
+        """Set the hatch style (for fills)."""
+        self._hatch = hatch
+
+    def get_hatch(self):
+        """Get the current hatch style."""
+        return self._hatch
+
+    def get_hatch_path(self, density=6.0):
+        """Return a `Path` for the current hatch."""
+        hatch = self.get_hatch()
+        if hatch is None:
+            return None
+        return Path.hatch(hatch, density)
+
+    def get_hatch_color(self):
+        """Get the hatch color."""
+        return self._hatch_color
+
+    def set_hatch_color(self, hatch_color):
+        """Set the hatch color."""
+        self._hatch_color = hatch_color
+
+    def get_hatch_linewidth(self):
+        """Get the hatch linewidth."""
+        return self._hatch_linewidth
+
+    def get_sketch_params(self):
+        """
+        Return the sketch parameters for the artist.
+
+        Returns
+        -------
+        sketch_params : tuple or `None`
+
+            A 3-tuple with the following elements:
+
+            * ``scale``: The amplitude of the wiggle perpendicular to the
+              source line.
+            * ``length``: The length of the wiggle along the line.
+            * ``randomness``: The scale factor by which the length is
+              shrunken or expanded.
+
+            May return `None` if no sketch parameters were set.
+        """
+        return self._sketch
+
+    def set_sketch_params(self, scale=None, length=None, randomness=None):
+        """
+        Set the sketch parameters.
+
+        Parameters
+        ----------
+        scale : float, optional
+            The amplitude of the wiggle perpendicular to the source line, in
+            pixels.  If scale is `None`, or not provided, no sketch filter will
+            be provided.
+        length : float, default: 128
+             The length of the wiggle along the line, in pixels.
+        randomness : float, default: 16
+            The scale factor by which the length is shrunken or expanded.
+        """
+        self._sketch = (
+            None if scale is None
+            else (scale, length or 128., randomness or 16.))
+
+
+class TimerBase:
+    """
+    A base class for providing timer events, useful for things animations.
+    Backends need to implement a few specific methods in order to use their
+    own timing mechanisms so that the timer events are integrated into their
+    event loops.
+
+    Subclasses must override the following methods:
+
+    - ``_timer_start``: Backend-specific code for starting the timer.
+    - ``_timer_stop``: Backend-specific code for stopping the timer.
+
+    Subclasses may additionally override the following methods:
+
+    - ``_timer_set_single_shot``: Code for setting the timer to single shot
+      operating mode, if supported by the timer object.  If not, the `Timer`
+      class itself will store the flag and the ``_on_timer`` method should be
+      overridden to support such behavior.
+
+    - ``_timer_set_interval``: Code for setting the interval on the timer, if
+      there is a method for doing so on the timer object.
+
+    - ``_on_timer``: The internal function that any timer object should call,
+      which will handle the task of running all callbacks that have been set.
+
+    Attributes
+    ----------
+    interval : scalar
+        The time between timer events in milliseconds. Default is 1000 ms.
+
+    single_shot : bool
+        Boolean flag indicating whether this timer should operate as single
+        shot (run once and then stop). Defaults to `False`.
+
+    callbacks : List[Tuple[callable, Tuple, Dict]]
+        Stores list of (func, args, kwargs) tuples that will be called upon
+        timer events. This list can be manipulated directly, or the
+        functions `add_callback` and `remove_callback` can be used.
+    """
+    def __init__(self, interval=None, callbacks=None):
+        #Initialize empty callbacks list and setup default settings if necssary
+        if callbacks is None:
+            self.callbacks = []
+        else:
+            self.callbacks = callbacks[:]  # Create a copy
+
+        if interval is None:
+            self._interval = 1000
+        else:
+            self._interval = interval
+
+        self._single = False
+
+        # Default attribute for holding the GUI-specific timer object
+        self._timer = None
+
+    def __del__(self):
+        """Need to stop timer and possibly disconnect timer."""
+        self._timer_stop()
+
+    def start(self, interval=None):
+        """
+        Start the timer object.
+
+        Parameters
+        ----------
+        interval : int, optional
+            Timer interval in milliseconds; overrides a previously set interval
+            if provided.
+        """
+        if interval is not None:
+            self.interval = interval
+        self._timer_start()
+
+    def stop(self):
+        """Stop the timer."""
+        self._timer_stop()
+
+    def _timer_start(self):
+        pass
+
+    def _timer_stop(self):
+        pass
+
+    @property
+    def interval(self):
+        return self._interval
+
+    @interval.setter
+    def interval(self, interval):
+        # Force to int since none of the backends actually support fractional
+        # milliseconds, and some error or give warnings.
+        interval = int(interval)
+        self._interval = interval
+        self._timer_set_interval()
+
+    @property
+    def single_shot(self):
+        return self._single
+
+    @single_shot.setter
+    def single_shot(self, ss):
+        self._single = ss
+        self._timer_set_single_shot()
+
+    def add_callback(self, func, *args, **kwargs):
+        """
+        Register *func* to be called by timer when the event fires. Any
+        additional arguments provided will be passed to *func*.
+
+        This function returns *func*, which makes it possible to use it as a
+        decorator.
+        """
+        self.callbacks.append((func, args, kwargs))
+        return func
+
+    def remove_callback(self, func, *args, **kwargs):
+        """
+        Remove *func* from list of callbacks.
+
+        *args* and *kwargs* are optional and used to distinguish between copies
+        of the same function registered to be called with different arguments.
+        This behavior is deprecated.  In the future, `*args, **kwargs` won't be
+        considered anymore; to keep a specific callback removable by itself,
+        pass it to `add_callback` as a `functools.partial` object.
+        """
+        if args or kwargs:
+            cbook.warn_deprecated(
+                "3.1", message="In a future version, Timer.remove_callback "
+                "will not take *args, **kwargs anymore, but remove all "
+                "callbacks where the callable matches; to keep a specific "
+                "callback removable by itself, pass it to add_callback as a "
+                "functools.partial object.")
+            self.callbacks.remove((func, args, kwargs))
+        else:
+            funcs = [c[0] for c in self.callbacks]
+            if func in funcs:
+                self.callbacks.pop(funcs.index(func))
+
+    def _timer_set_interval(self):
+        """Used to set interval on underlying timer object."""
+
+    def _timer_set_single_shot(self):
+        """Used to set single shot on underlying timer object."""
+
+    def _on_timer(self):
+        """
+        Runs all function that have been registered as callbacks. Functions
+        can return False (or 0) if they should not be called any more. If there
+        are no callbacks, the timer is automatically stopped.
+        """
+        for func, args, kwargs in self.callbacks:
+            ret = func(*args, **kwargs)
+            # docstring above explains why we use `if ret == 0` here,
+            # instead of `if not ret`.
+            # This will also catch `ret == False` as `False == 0`
+            # but does not annoy the linters
+            # https://docs.python.org/3/library/stdtypes.html#boolean-values
+            if ret == 0:
+                self.callbacks.remove((func, args, kwargs))
+
+        if len(self.callbacks) == 0:
+            self.stop()
+
+
+class Event:
+    """
+    A matplotlib event.  Attach additional attributes as defined in
+    :meth:`FigureCanvasBase.mpl_connect`.  The following attributes
+    are defined and shown with their default values
+
+    Attributes
+    ----------
+    name : str
+        the event name
+
+    canvas : `FigureCanvasBase`
+        the backend-specific canvas instance generating the event
+
+    guiEvent
+        the GUI event that triggered the matplotlib event
+
+    """
+    def __init__(self, name, canvas, guiEvent=None):
+        self.name = name
+        self.canvas = canvas
+        self.guiEvent = guiEvent
+
+
+class DrawEvent(Event):
+    """
+    An event triggered by a draw operation on the canvas
+
+    In most backends callbacks subscribed to this callback will be
+    fired after the rendering is complete but before the screen is
+    updated.  Any extra artists drawn to the canvas's renderer will
+    be reflected without an explicit call to ``blit``.
+
+    .. warning::
+
+       Calling ``canvas.draw`` and ``canvas.blit`` in these callbacks may
+       not be safe with all backends and may cause infinite recursion.
+
+    In addition to the :class:`Event` attributes, the following event
+    attributes are defined:
+
+    Attributes
+    ----------
+    renderer : `RendererBase`
+        the renderer for the draw event
+
+    """
+    def __init__(self, name, canvas, renderer):
+        Event.__init__(self, name, canvas)
+        self.renderer = renderer
+
+
+class ResizeEvent(Event):
+    """
+    An event triggered by a canvas resize
+
+    In addition to the :class:`Event` attributes, the following event
+    attributes are defined:
+
+    Attributes
+    ----------
+    width : int
+        Width of the canvas in pixels.
+    height : int
+        Height of the canvas in pixels.
+    """
+    def __init__(self, name, canvas):
+        Event.__init__(self, name, canvas)
+        self.width, self.height = canvas.get_width_height()
+
+
+class CloseEvent(Event):
+    """An event triggered by a figure being closed."""
+
+
+class LocationEvent(Event):
+    """
+    An event that has a screen location.
+
+    The following additional attributes are defined and shown with
+    their default values.
+
+    In addition to the :class:`Event` attributes, the following
+    event attributes are defined:
+
+    Attributes
+    ----------
+    x : int
+        x position - pixels from left of canvas.
+    y : int
+        y position - pixels from bottom of canvas.
+    inaxes : `~.axes.Axes` or None
+        The `~.axes.Axes` instance over which the mouse is, if any.
+    xdata : float or None
+        x data coordinate of the mouse.
+    ydata : float or None
+        y data coordinate of the mouse.
+    """
+
+    lastevent = None  # the last event that was triggered before this one
+
+    def __init__(self, name, canvas, x, y, guiEvent=None):
+        """
+        (*x*, *y*) in figure coords ((0, 0) = bottom left).
+        """
+        Event.__init__(self, name, canvas, guiEvent=guiEvent)
+        # x position - pixels from left of canvas
+        self.x = int(x) if x is not None else x
+        # y position - pixels from right of canvas
+        self.y = int(y) if y is not None else y
+        self.inaxes = None  # the Axes instance if mouse us over axes
+        self.xdata = None   # x coord of mouse in data coords
+        self.ydata = None   # y coord of mouse in data coords
+
+        if x is None or y is None:
+            # cannot check if event was in axes if no (x, y) info
+            self._update_enter_leave()
+            return
+
+        if self.canvas.mouse_grabber is None:
+            self.inaxes = self.canvas.inaxes((x, y))
+        else:
+            self.inaxes = self.canvas.mouse_grabber
+
+        if self.inaxes is not None:
+            try:
+                trans = self.inaxes.transData.inverted()
+                xdata, ydata = trans.transform((x, y))
+            except ValueError:
+                pass
+            else:
+                self.xdata = xdata
+                self.ydata = ydata
+
+        self._update_enter_leave()
+
+    def _update_enter_leave(self):
+        'process the figure/axes enter leave events'
+        if LocationEvent.lastevent is not None:
+            last = LocationEvent.lastevent
+            if last.inaxes != self.inaxes:
+                # process axes enter/leave events
+                try:
+                    if last.inaxes is not None:
+                        last.canvas.callbacks.process('axes_leave_event', last)
+                except Exception:
+                    pass
+                    # See ticket 2901582.
+                    # I think this is a valid exception to the rule
+                    # against catching all exceptions; if anything goes
+                    # wrong, we simply want to move on and process the
+                    # current event.
+                if self.inaxes is not None:
+                    self.canvas.callbacks.process('axes_enter_event', self)
+
+        else:
+            # process a figure enter event
+            if self.inaxes is not None:
+                self.canvas.callbacks.process('axes_enter_event', self)
+
+        LocationEvent.lastevent = self
+
+
+class MouseButton(IntEnum):
+    LEFT = 1
+    MIDDLE = 2
+    RIGHT = 3
+    BACK = 8
+    FORWARD = 9
+
+
+class MouseEvent(LocationEvent):
+    """
+    A mouse event ('button_press_event',
+                   'button_release_event',
+                   'scroll_event',
+                   'motion_notify_event').
+
+    In addition to the :class:`Event` and :class:`LocationEvent`
+    attributes, the following attributes are defined:
+
+    Attributes
+    ----------
+    button : {None, MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, \
+'up', 'down'}
+        The button pressed. 'up' and 'down' are used for scroll events.
+        Note that in the nbagg backend, both the middle and right clicks
+        return RIGHT since right clicking will bring up the context menu in
+        some browsers.
+        Note that LEFT and RIGHT actually refer to the "primary" and
+        "secondary" buttons, i.e. if the user inverts their left and right
+        buttons ("left-handed setting") then the LEFT button will be the one
+        physically on the right.
+
+    key : None or str
+        The key pressed when the mouse event triggered, e.g. 'shift'.
+        See `KeyEvent`.
+
+    step : int
+        The number of scroll steps (positive for 'up', negative for 'down').
+        This applies only to 'scroll_event' and defaults to 0 otherwise.
+
+    dblclick : bool
+        Whether the event is a double-click. This applies only to
+        'button_press_event' and is False otherwise. In particular, it's
+        not used in 'button_release_event'.
+
+    Examples
+    --------
+    ::
+
+        def on_press(event):
+            print('you pressed', event.button, event.xdata, event.ydata)
+
+        cid = fig.canvas.mpl_connect('button_press_event', on_press)
+    """
+
+    def __init__(self, name, canvas, x, y, button=None, key=None,
+                 step=0, dblclick=False, guiEvent=None):
+        """
+        (*x*, *y*) in figure coords ((0, 0) = bottom left)
+        button pressed None, 1, 2, 3, 'up', 'down'
+        """
+        LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
+        if button in MouseButton.__members__.values():
+            button = MouseButton(button)
+        self.button = button
+        self.key = key
+        self.step = step
+        self.dblclick = dblclick
+
+    def __str__(self):
+        return (f"{self.name}: "
+                f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
+                f"button={self.button} dblclick={self.dblclick} "
+                f"inaxes={self.inaxes}")
+
+
+class PickEvent(Event):
+    """
+    a pick event, fired when the user picks a location on the canvas
+    sufficiently close to an artist.
+
+    Attrs: all the :class:`Event` attributes plus
+
+    Attributes
+    ----------
+    mouseevent : `MouseEvent`
+        the mouse event that generated the pick
+
+    artist : `matplotlib.artist.Artist`
+        the picked artist
+
+    other
+        extra class dependent attrs -- e.g., a
+        :class:`~matplotlib.lines.Line2D` pick may define different
+        extra attributes than a
+        :class:`~matplotlib.collections.PatchCollection` pick event
+
+    Examples
+    --------
+    ::
+        ax.plot(np.rand(100), 'o', picker=5)  # 5 points tolerance
+
+        def on_pick(event):
+            line = event.artist
+            xdata, ydata = line.get_data()
+            ind = event.ind
+            print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
+
+        cid = fig.canvas.mpl_connect('pick_event', on_pick)
+    """
+    def __init__(self, name, canvas, mouseevent, artist,
+                 guiEvent=None, **kwargs):
+        Event.__init__(self, name, canvas, guiEvent)
+        self.mouseevent = mouseevent
+        self.artist = artist
+        self.__dict__.update(kwargs)
+
+
+class KeyEvent(LocationEvent):
+    """
+    A key event (key press, key release).
+
+    Attach additional attributes as defined in
+    :meth:`FigureCanvasBase.mpl_connect`.
+
+    In addition to the :class:`Event` and :class:`LocationEvent`
+    attributes, the following attributes are defined:
+
+    Attributes
+    ----------
+    key : None or str
+        the key(s) pressed. Could be **None**, a single case sensitive ascii
+        character ("g", "G", "#", etc.), a special key
+        ("control", "shift", "f1", "up", etc.) or a
+        combination of the above (e.g., "ctrl+alt+g", "ctrl+alt+G").
+
+    Notes
+    -----
+    Modifier keys will be prefixed to the pressed key and will be in the order
+    "ctrl", "alt", "super". The exception to this rule is when the pressed key
+    is itself a modifier key, therefore "ctrl+alt" and "alt+control" can both
+    be valid key values.
+
+    Examples
+    --------
+    ::
+
+        def on_key(event):
+            print('you pressed', event.key, event.xdata, event.ydata)
+
+        cid = fig.canvas.mpl_connect('key_press_event', on_key)
+    """
+    def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
+        LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
+        self.key = key
+
+
+def _get_renderer(figure, print_method, *, draw_disabled=False):
+    """
+    Get the renderer that would be used to save a `~.Figure`, and cache it on
+    the figure.
+
+    If *draw_disabled* is True, additionally replace draw_foo methods on
+    *renderer* by no-ops.  This is used by the tight-bbox-saving renderer,
+    which needs to walk through the artist tree to compute the tight-bbox, but
+    for which the output file may be closed early.
+    """
+    # This is implemented by triggering a draw, then immediately jumping out of
+    # Figure.draw() by raising an exception.
+
+    class Done(Exception):
+        pass
+
+    def _draw(renderer): raise Done(renderer)
+
+    with cbook._setattr_cm(figure, draw=_draw):
+        try:
+            print_method(io.BytesIO())
+        except Done as exc:
+            renderer, = figure._cachedRenderer, = exc.args
+
+    if draw_disabled:
+        for meth_name in dir(RendererBase):
+            if meth_name.startswith("draw_"):
+                setattr(renderer, meth_name, lambda *args, **kwargs: None)
+
+    return renderer
+
+
+def _is_non_interactive_terminal_ipython(ip):
+    """
+    Return whether we are in a a terminal IPython, but non interactive.
+
+    When in _terminal_ IPython, ip.parent will have and `interact` attribute,
+    if this attribute is False we do not setup eventloop integration as the
+    user will _not_ interact with IPython. In all other case (ZMQKernel, or is
+    interactive), we do.
+    """
+    return (hasattr(ip, 'parent')
+        and (ip.parent is not None)
+        and getattr(ip.parent, 'interact', None) is False)
+
+
+class FigureCanvasBase:
+    """
+    The canvas the figure renders into.
+
+    Public attributes
+
+    Attributes
+    ----------
+    figure : `matplotlib.figure.Figure`
+        A high-level figure instance
+    """
+
+    # Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an
+    # interactive framework is required, or None otherwise.
+    required_interactive_framework = None
+
+    events = [
+        'resize_event',
+        'draw_event',
+        'key_press_event',
+        'key_release_event',
+        'button_press_event',
+        'button_release_event',
+        'scroll_event',
+        'motion_notify_event',
+        'pick_event',
+        'idle_event',
+        'figure_enter_event',
+        'figure_leave_event',
+        'axes_enter_event',
+        'axes_leave_event',
+        'close_event'
+    ]
+
+    fixed_dpi = None
+
+    filetypes = _default_filetypes
+    if _has_pil:
+        # JPEG support
+        register_backend('jpg', 'matplotlib.backends.backend_agg',
+                         'Joint Photographic Experts Group')
+        register_backend('jpeg', 'matplotlib.backends.backend_agg',
+                         'Joint Photographic Experts Group')
+        # TIFF support
+        register_backend('tif', 'matplotlib.backends.backend_agg',
+                         'Tagged Image File Format')
+        register_backend('tiff', 'matplotlib.backends.backend_agg',
+                         'Tagged Image File Format')
+
+    @cbook._classproperty
+    def supports_blit(cls):
+        return (hasattr(cls, "copy_from_bbox")
+                and hasattr(cls, "restore_region"))
+
+    def __init__(self, figure):
+        self._fix_ipython_backend2gui()
+        self._is_idle_drawing = True
+        self._is_saving = False
+        figure.set_canvas(self)
+        self.figure = figure
+        # a dictionary from event name to a dictionary that maps cid->func
+        self.callbacks = cbook.CallbackRegistry()
+        self.widgetlock = widgets.LockDraw()
+        self._button = None  # the button pressed
+        self._key = None  # the key pressed
+        self._lastx, self._lasty = None, None
+        self.button_pick_id = self.mpl_connect('button_press_event', self.pick)
+        self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick)
+        self.mouse_grabber = None  # the axes currently grabbing mouse
+        self.toolbar = None  # NavigationToolbar2 will set me
+        self._is_idle_drawing = False
+
+    @classmethod
+    @functools.lru_cache()
+    def _fix_ipython_backend2gui(cls):
+        # Fix hard-coded module -> toolkit mapping in IPython (used for
+        # `ipython --auto`).  This cannot be done at import time due to
+        # ordering issues, so we do it when creating a canvas, and should only
+        # be done once per class (hence the `lru_cache(1)`).
+        if "IPython" not in sys.modules:
+            return
+        import IPython
+        ip = IPython.get_ipython()
+        if not ip:
+            return
+        from IPython.core import pylabtools as pt
+        if (not hasattr(pt, "backend2gui")
+                or not hasattr(ip, "enable_matplotlib")):
+            # In case we ever move the patch to IPython and remove these APIs,
+            # don't break on our side.
+            return
+        rif = getattr(cls, "required_interactive_framework", None)
+        backend2gui_rif = {"qt5": "qt", "qt4": "qt", "gtk3": "gtk3",
+                           "wx": "wx", "macosx": "osx"}.get(rif)
+        if backend2gui_rif:
+            if _is_non_interactive_terminal_ipython(ip):
+                ip.enable_gui(backend2gui_rif)
+
+    @contextmanager
+    def _idle_draw_cntx(self):
+        self._is_idle_drawing = True
+        try:
+            yield
+        finally:
+            self._is_idle_drawing = False
+
+    def is_saving(self):
+        """
+        Returns whether the renderer is in the process of saving
+        to a file, rather than rendering for an on-screen buffer.
+        """
+        return self._is_saving
+
+    def pick(self, mouseevent):
+        if not self.widgetlock.locked():
+            self.figure.pick(mouseevent)
+
+    def blit(self, bbox=None):
+        """Blit the canvas in bbox (default entire canvas)."""
+
+    def resize(self, w, h):
+        """Set the canvas size in pixels."""
+
+    def draw_event(self, renderer):
+        """Pass a `DrawEvent` to all functions connected to ``draw_event``."""
+        s = 'draw_event'
+        event = DrawEvent(s, self, renderer)
+        self.callbacks.process(s, event)
+
+    def resize_event(self):
+        """
+        Pass a `ResizeEvent` to all functions connected to ``resize_event``.
+        """
+        s = 'resize_event'
+        event = ResizeEvent(s, self)
+        self.callbacks.process(s, event)
+        self.draw_idle()
+
+    def close_event(self, guiEvent=None):
+        """
+        Pass a `CloseEvent` to all functions connected to ``close_event``.
+        """
+        s = 'close_event'
+        try:
+            event = CloseEvent(s, self, guiEvent=guiEvent)
+            self.callbacks.process(s, event)
+        except (TypeError, AttributeError):
+            pass
+            # Suppress the TypeError when the python session is being killed.
+            # It may be that a better solution would be a mechanism to
+            # disconnect all callbacks upon shutdown.
+            # AttributeError occurs on OSX with qt4agg upon exiting
+            # with an open window; 'callbacks' attribute no longer exists.
+
+    def key_press_event(self, key, guiEvent=None):
+        """
+        Pass a `KeyEvent` to all functions connected to ``key_press_event``.
+        """
+        self._key = key
+        s = 'key_press_event'
+        event = KeyEvent(
+            s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
+        self.callbacks.process(s, event)
+
+    def key_release_event(self, key, guiEvent=None):
+        """
+        Pass a `KeyEvent` to all functions connected to ``key_release_event``.
+        """
+        s = 'key_release_event'
+        event = KeyEvent(
+            s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
+        self.callbacks.process(s, event)
+        self._key = None
+
+    def pick_event(self, mouseevent, artist, **kwargs):
+        """
+        This method will be called by artists who are picked and will
+        fire off :class:`PickEvent` callbacks registered listeners
+        """
+        s = 'pick_event'
+        event = PickEvent(s, self, mouseevent, artist,
+                          guiEvent=mouseevent.guiEvent,
+                          **kwargs)
+        self.callbacks.process(s, event)
+
+    def scroll_event(self, x, y, step, guiEvent=None):
+        """
+        Backend derived classes should call this function on any
+        scroll wheel event.  (*x*, *y*) are the canvas coords ((0, 0) is lower
+        left).  button and key are as defined in MouseEvent.
+
+        This method will be call all functions connected to the
+        'scroll_event' with a :class:`MouseEvent` instance.
+        """
+        if step >= 0:
+            self._button = 'up'
+        else:
+            self._button = 'down'
+        s = 'scroll_event'
+        mouseevent = MouseEvent(s, self, x, y, self._button, self._key,
+                                step=step, guiEvent=guiEvent)
+        self.callbacks.process(s, mouseevent)
+
+    def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
+        """
+        Backend derived classes should call this function on any mouse
+        button press.  (*x*, *y*) are the canvas coords ((0, 0) is lower left).
+        button and key are as defined in :class:`MouseEvent`.
+
+        This method will be call all functions connected to the
+        'button_press_event' with a :class:`MouseEvent` instance.
+        """
+        self._button = button
+        s = 'button_press_event'
+        mouseevent = MouseEvent(s, self, x, y, button, self._key,
+                                dblclick=dblclick, guiEvent=guiEvent)
+        self.callbacks.process(s, mouseevent)
+
+    def button_release_event(self, x, y, button, guiEvent=None):
+        """
+        Backend derived classes should call this function on any mouse
+        button release.
+
+        This method will call all functions connected to the
+        'button_release_event' with a :class:`MouseEvent` instance.
+
+        Parameters
+        ----------
+        x : float
+            The canvas coordinates where 0=left.
+        y : float
+            The canvas coordinates where 0=bottom.
+        guiEvent
+            The native UI event that generated the Matplotlib event.
+        """
+        s = 'button_release_event'
+        event = MouseEvent(s, self, x, y, button, self._key, guiEvent=guiEvent)
+        self.callbacks.process(s, event)
+        self._button = None
+
+    def motion_notify_event(self, x, y, guiEvent=None):
+        """
+        Backend derived classes should call this function on any
+        motion-notify-event.
+
+        This method will call all functions connected to the
+        'motion_notify_event' with a :class:`MouseEvent` instance.
+
+        Parameters
+        ----------
+        x : float
+            The canvas coordinates where 0=left.
+        y : float
+            The canvas coordinates where 0=bottom.
+        guiEvent
+            The native UI event that generated the Matplotlib event.
+        """
+        self._lastx, self._lasty = x, y
+        s = 'motion_notify_event'
+        event = MouseEvent(s, self, x, y, self._button, self._key,
+                           guiEvent=guiEvent)
+        self.callbacks.process(s, event)
+
+    def leave_notify_event(self, guiEvent=None):
+        """
+        Backend derived classes should call this function when leaving
+        canvas
+
+        Parameters
+        ----------
+        guiEvent
+            The native UI event that generated the Matplotlib event.
+        """
+        self.callbacks.process('figure_leave_event', LocationEvent.lastevent)
+        LocationEvent.lastevent = None
+        self._lastx, self._lasty = None, None
+
+    def enter_notify_event(self, guiEvent=None, xy=None):
+        """
+        Backend derived classes should call this function when entering
+        canvas
+
+        Parameters
+        ----------
+        guiEvent
+            The native UI event that generated the Matplotlib event.
+        xy : (float, float)
+            The coordinate location of the pointer when the canvas is entered.
+        """
+        if xy is not None:
+            x, y = xy
+            self._lastx, self._lasty = x, y
+        else:
+            x = None
+            y = None
+            cbook.warn_deprecated(
+                '3.0', message='enter_notify_event expects a location but '
+                'your backend did not pass one.')
+
+        event = LocationEvent('figure_enter_event', self, x, y, guiEvent)
+        self.callbacks.process('figure_enter_event', event)
+
+    def inaxes(self, xy):
+        """
+        Return the topmost visible `~.axes.Axes` containing the point *xy*.
+
+        Parameters
+        ----------
+        xy : tuple or list
+            (x, y) coordinates.
+            x position - pixels from left of canvas.
+            y position - pixels from bottom of canvas.
+
+        Returns
+        -------
+        axes : `~matplotlib.axes.Axes` or None
+            The topmost visible axes containing the point, or None if no axes.
+        """
+        axes_list = [a for a in self.figure.get_axes()
+                     if a.patch.contains_point(xy) and a.get_visible()]
+        if axes_list:
+            axes = cbook._topmost_artist(axes_list)
+        else:
+            axes = None
+
+        return axes
+
+    def grab_mouse(self, ax):
+        """
+        Set the child axes which are currently grabbing the mouse events.
+        Usually called by the widgets themselves.
+        It is an error to call this if the mouse is already grabbed by
+        another axes.
+        """
+        if self.mouse_grabber not in (None, ax):
+            raise RuntimeError("Another Axes already grabs mouse input")
+        self.mouse_grabber = ax
+
+    def release_mouse(self, ax):
+        """
+        Release the mouse grab held by the axes, ax.
+        Usually called by the widgets.
+        It is ok to call this even if you ax doesn't have the mouse
+        grab currently.
+        """
+        if self.mouse_grabber is ax:
+            self.mouse_grabber = None
+
+    def draw(self, *args, **kwargs):
+        """Render the :class:`~matplotlib.figure.Figure`."""
+
+    def draw_idle(self, *args, **kwargs):
+        """
+        Request a widget redraw once control returns to the GUI event loop.
+
+        Even if multiple calls to `draw_idle` occur before control returns
+        to the GUI event loop, the figure will only be rendered once.
+
+        Notes
+        -----
+        Backends may choose to override the method and implement their own
+        strategy to prevent multiple renderings.
+
+        """
+        if not self._is_idle_drawing:
+            with self._idle_draw_cntx():
+                self.draw(*args, **kwargs)
+
+    @cbook.deprecated("3.2")
+    def draw_cursor(self, event):
+        """
+        Draw a cursor in the event.axes if inaxes is not None.  Use
+        native GUI drawing for efficiency if possible
+        """
+
+    def get_width_height(self):
+        """
+        Return the figure width and height in points or pixels
+        (depending on the backend), truncated to integers
+        """
+        return int(self.figure.bbox.width), int(self.figure.bbox.height)
+
+    @classmethod
+    def get_supported_filetypes(cls):
+        """Return dict of savefig file formats supported by this backend."""
+        return cls.filetypes
+
+    @classmethod
+    def get_supported_filetypes_grouped(cls):
+        """
+        Return a dict of savefig file formats supported by this backend,
+        where the keys are a file type name, such as 'Joint Photographic
+        Experts Group', and the values are a list of filename extensions used
+        for that filetype, such as ['jpg', 'jpeg'].
+        """
+        groupings = {}
+        for ext, name in cls.filetypes.items():
+            groupings.setdefault(name, []).append(ext)
+            groupings[name].sort()
+        return groupings
+
+    def _get_output_canvas(self, fmt):
+        """
+        Return a canvas suitable for saving figures to a specified file format.
+
+        If necessary, this function will switch to a registered backend that
+        supports the format.
+        """
+        # Return the current canvas if it supports the requested format.
+        if hasattr(self, 'print_{}'.format(fmt)):
+            return self
+        # Return a default canvas for the requested format, if it exists.
+        canvas_class = get_registered_canvas_class(fmt)
+        if canvas_class:
+            return self.switch_backends(canvas_class)
+        # Else report error for unsupported format.
+        raise ValueError(
+            "Format {!r} is not supported (supported formats: {})"
+            .format(fmt, ", ".join(sorted(self.get_supported_filetypes()))))
+
+    def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
+                     orientation='portrait', format=None,
+                     *, bbox_inches=None, **kwargs):
+        """
+        Render the figure to hardcopy. Set the figure patch face and edge
+        colors.  This is useful because some of the GUIs have a gray figure
+        face color background and you'll probably want to override this on
+        hardcopy.
+
+        Parameters
+        ----------
+        filename
+            can also be a file object on image backends
+
+        orientation : {'landscape', 'portrait'}, default: 'portrait'
+            only currently applies to PostScript printing.
+
+        dpi : scalar, optional
+            the dots per inch to save the figure in; if None, use savefig.dpi
+
+        facecolor : color, default: :rc:`savefig.facecolor`
+            The facecolor of the figure.
+
+        edgecolor : color, default: :rc:`savefig.edgecolor`
+            The edgecolor of the figure.
+
+        format : str, optional
+            Force a specific file format. If not given, the format is inferred
+            from the *filename* extension, and if that fails from
+            :rc:`savefig.format`.
+
+        bbox_inches : 'tight' or `~matplotlib.transforms.Bbox`, \
+default: :rc:`savefig.bbox`
+            Bbox in inches. Only the given portion of the figure is
+            saved. If 'tight', try to figure out the tight bbox of
+            the figure.
+
+        pad_inches : float, default: :rc:`savefig.pad_inches`
+            Amount of padding around the figure when *bbox_inches* is 'tight'.
+
+        bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional
+            A list of extra artists that will be considered when the
+            tight bbox is calculated.
+
+        """
+        if format is None:
+            # get format from filename, or from backend's default filetype
+            if isinstance(filename, os.PathLike):
+                filename = os.fspath(filename)
+            if isinstance(filename, str):
+                format = os.path.splitext(filename)[1][1:]
+            if format is None or format == '':
+                format = self.get_default_filetype()
+                if isinstance(filename, str):
+                    filename = filename.rstrip('.') + '.' + format
+        format = format.lower()
+
+        # get canvas object and print method for format
+        canvas = self._get_output_canvas(format)
+        print_method = getattr(canvas, 'print_%s' % format)
+
+        if dpi is None:
+            dpi = rcParams['savefig.dpi']
+        if dpi == 'figure':
+            dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)
+
+        # Remove the figure manager, if any, to avoid resizing the GUI widget.
+        # Some code (e.g. Figure.show) differentiates between having *no*
+        # manager and a *None* manager, which should be fixed at some point,
+        # but this should be fine.
+        with cbook._setattr_cm(self, _is_saving=True, manager=None), \
+                cbook._setattr_cm(self.figure, dpi=dpi):
+
+            if facecolor is None:
+                facecolor = rcParams['savefig.facecolor']
+            if edgecolor is None:
+                edgecolor = rcParams['savefig.edgecolor']
+
+            origfacecolor = self.figure.get_facecolor()
+            origedgecolor = self.figure.get_edgecolor()
+
+            self.figure.set_facecolor(facecolor)
+            self.figure.set_edgecolor(edgecolor)
+
+            if bbox_inches is None:
+                bbox_inches = rcParams['savefig.bbox']
+
+            if bbox_inches:
+                if bbox_inches == "tight":
+                    renderer = _get_renderer(
+                        self.figure,
+                        functools.partial(
+                            print_method, dpi=dpi, orientation=orientation),
+                        draw_disabled=True)
+                    self.figure.draw(renderer)
+                    bbox_artists = kwargs.pop("bbox_extra_artists", None)
+                    bbox_inches = self.figure.get_tightbbox(renderer,
+                            bbox_extra_artists=bbox_artists)
+                    pad = kwargs.pop("pad_inches", None)
+                    if pad is None:
+                        pad = rcParams['savefig.pad_inches']
+
+                    bbox_inches = bbox_inches.padded(pad)
+
+                # call adjust_bbox to save only the given area
+                restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
+                                                      canvas.fixed_dpi)
+
+                _bbox_inches_restore = (bbox_inches, restore_bbox)
+            else:
+                _bbox_inches_restore = None
+
+            try:
+                result = print_method(
+                    filename,
+                    dpi=dpi,
+                    facecolor=facecolor,
+                    edgecolor=edgecolor,
+                    orientation=orientation,
+                    bbox_inches_restore=_bbox_inches_restore,
+                    **kwargs)
+            finally:
+                if bbox_inches and restore_bbox:
+                    restore_bbox()
+
+                self.figure.set_facecolor(origfacecolor)
+                self.figure.set_edgecolor(origedgecolor)
+                self.figure.set_canvas(self)
+            return result
+
+    @classmethod
+    def get_default_filetype(cls):
+        """
+        Get the default savefig file format as specified in rcParam
+        ``savefig.format``. Returned string excludes period. Overridden
+        in backends that only support a single file type.
+        """
+        return rcParams['savefig.format']
+
+    def get_window_title(self):
+        """
+        Get the title text of the window containing the figure.
+        Return None if there is no window (e.g., a PS backend).
+        """
+        if hasattr(self, "manager"):
+            return self.manager.get_window_title()
+
+    def set_window_title(self, title):
+        """
+        Set the title text of the window containing the figure.  Note that
+        this has no effect if there is no window (e.g., a PS backend).
+        """
+        if hasattr(self, "manager"):
+            self.manager.set_window_title(title)
+
+    def get_default_filename(self):
+        """
+        Return a string, which includes extension, suitable for use as
+        a default filename.
+        """
+        default_basename = self.get_window_title() or 'image'
+        default_basename = default_basename.replace(' ', '_')
+        default_filetype = self.get_default_filetype()
+        default_filename = default_basename + '.' + default_filetype
+        return default_filename
+
+    def switch_backends(self, FigureCanvasClass):
+        """
+        Instantiate an instance of FigureCanvasClass
+
+        This is used for backend switching, e.g., to instantiate a
+        FigureCanvasPS from a FigureCanvasGTK.  Note, deep copying is
+        not done, so any changes to one of the instances (e.g., setting
+        figure size or line props), will be reflected in the other
+        """
+        newCanvas = FigureCanvasClass(self.figure)
+        newCanvas._is_saving = self._is_saving
+        return newCanvas
+
+    def mpl_connect(self, s, func):
+        """
+        Bind function *func* to event *s*.
+
+        Parameters
+        ----------
+        s : str
+            One of the following events ids:
+
+            - 'button_press_event'
+            - 'button_release_event'
+            - 'draw_event'
+            - 'key_press_event'
+            - 'key_release_event'
+            - 'motion_notify_event'
+            - 'pick_event'
+            - 'resize_event'
+            - 'scroll_event'
+            - 'figure_enter_event',
+            - 'figure_leave_event',
+            - 'axes_enter_event',
+            - 'axes_leave_event'
+            - 'close_event'.
+
+        func : callable
+            The callback function to be executed, which must have the
+            signature::
+
+                def func(event: Event) -> Any
+
+            For the location events (button and key press/release), if the
+            mouse is over the axes, the ``inaxes`` attribute of the event will
+            be set to the `~matplotlib.axes.Axes` the event occurs is over, and
+            additionally, the variables ``xdata`` and ``ydata`` attributes will
+            be set to the mouse location in data coordinates.  See `.KeyEvent`
+            and `.MouseEvent` for more info.
+
+        Returns
+        -------
+        cid
+            A connection id that can be used with
+            `.FigureCanvasBase.mpl_disconnect`.
+
+        Examples
+        --------
+        ::
+
+            def on_press(event):
+                print('you pressed', event.button, event.xdata, event.ydata)
+
+            cid = canvas.mpl_connect('button_press_event', on_press)
+        """
+
+        return self.callbacks.connect(s, func)
+
+    def mpl_disconnect(self, cid):
+        """
+        Disconnect the callback with id *cid*.
+
+        Examples
+        --------
+        ::
+
+            cid = canvas.mpl_connect('button_press_event', on_press)
+            # ... later
+            canvas.mpl_disconnect(cid)
+        """
+        return self.callbacks.disconnect(cid)
+
+    def new_timer(self, *args, **kwargs):
+        """
+        Create a new backend-specific subclass of `.Timer`.
+
+        This is useful for getting periodic events through the backend's native
+        event loop.  Implemented only for backends with GUIs.
+
+        Other Parameters
+        ----------------
+        interval : scalar
+            Timer interval in milliseconds
+
+        callbacks : List[Tuple[callable, Tuple, Dict]]
+            Sequence of (func, args, kwargs) where ``func(*args, **kwargs)``
+            will be executed by the timer every *interval*.
+
+            Callbacks which return ``False`` or ``0`` will be removed from the
+            timer.
+
+        Examples
+        --------
+        >>> timer = fig.canvas.new_timer(callbacks=[(f1, (1, ), {'a': 3}),])
+        """
+        return TimerBase(*args, **kwargs)
+
+    def flush_events(self):
+        """
+        Flush the GUI events for the figure.
+
+        Interactive backends need to reimplement this method.
+        """
+
+    def start_event_loop(self, timeout=0):
+        """
+        Start a blocking event loop.
+
+        Such an event loop is used by interactive functions, such as `ginput`
+        and `waitforbuttonpress`, to wait for events.
+
+        The event loop blocks until a callback function triggers
+        `stop_event_loop`, or *timeout* is reached.
+
+        If *timeout* is negative, never timeout.
+
+        Only interactive backends need to reimplement this method and it relies
+        on `flush_events` being properly implemented.
+
+        Interactive backends should implement this in a more native way.
+        """
+        if timeout <= 0:
+            timeout = np.inf
+        timestep = 0.01
+        counter = 0
+        self._looping = True
+        while self._looping and counter * timestep < timeout:
+            self.flush_events()
+            time.sleep(timestep)
+            counter += 1
+
+    def stop_event_loop(self):
+        """
+        Stop the current blocking event loop.
+
+        Interactive backends need to reimplement this to match
+        `start_event_loop`
+        """
+        self._looping = False
+
+
+def key_press_handler(event, canvas, toolbar=None):
+    """
+    Implement the default Matplotlib key bindings for the canvas and toolbar
+    described at :ref:`key-event-handling`.
+
+    Parameters
+    ----------
+    event : :class:`KeyEvent`
+        a key press/release event
+    canvas : :class:`FigureCanvasBase`
+        the backend-specific canvas instance
+    toolbar : :class:`NavigationToolbar2`
+        the navigation cursor toolbar
+    """
+    # these bindings happen whether you are over an axes or not
+
+    if event.key is None:
+        return
+
+    # Load key-mappings from rcParams.
+    fullscreen_keys = rcParams['keymap.fullscreen']
+    home_keys = rcParams['keymap.home']
+    back_keys = rcParams['keymap.back']
+    forward_keys = rcParams['keymap.forward']
+    pan_keys = rcParams['keymap.pan']
+    zoom_keys = rcParams['keymap.zoom']
+    save_keys = rcParams['keymap.save']
+    quit_keys = rcParams['keymap.quit']
+    grid_keys = rcParams['keymap.grid']
+    grid_minor_keys = rcParams['keymap.grid_minor']
+    toggle_yscale_keys = rcParams['keymap.yscale']
+    toggle_xscale_keys = rcParams['keymap.xscale']
+    all_keys = rcParams['keymap.all_axes']
+
+    # toggle fullscreen mode ('f', 'ctrl + f')
+    if event.key in fullscreen_keys:
+        try:
+            canvas.manager.full_screen_toggle()
+        except AttributeError:
+            pass
+
+    # quit the figure (default key 'ctrl+w')
+    if event.key in quit_keys:
+        Gcf.destroy_fig(canvas.figure)
+
+    if toolbar is not None:
+        # home or reset mnemonic  (default key 'h', 'home' and 'r')
+        if event.key in home_keys:
+            toolbar.home()
+        # forward / backward keys to enable left handed quick navigation
+        # (default key for backward: 'left', 'backspace' and 'c')
+        elif event.key in back_keys:
+            toolbar.back()
+        # (default key for forward: 'right' and 'v')
+        elif event.key in forward_keys:
+            toolbar.forward()
+        # pan mnemonic (default key 'p')
+        elif event.key in pan_keys:
+            toolbar.pan()
+            toolbar._update_cursor(event)
+        # zoom mnemonic (default key 'o')
+        elif event.key in zoom_keys:
+            toolbar.zoom()
+            toolbar._update_cursor(event)
+        # saving current figure (default key 's')
+        elif event.key in save_keys:
+            toolbar.save_figure()
+
+    if event.inaxes is None:
+        return
+
+    # these bindings require the mouse to be over an axes to trigger
+    def _get_uniform_gridstate(ticks):
+        # Return True/False if all grid lines are on or off, None if they are
+        # not all in the same state.
+        if all(tick.gridline.get_visible() for tick in ticks):
+            return True
+        elif not any(tick.gridline.get_visible() for tick in ticks):
+            return False
+        else:
+            return None
+
+    ax = event.inaxes
+    # toggle major grids in current axes (default key 'g')
+    # Both here and below (for 'G'), we do nothing if *any* grid (major or
+    # minor, x or y) is not in a uniform state, to avoid messing up user
+    # customization.
+    if (event.key in grid_keys
+            # Exclude minor grids not in a uniform state.
+            and None not in [_get_uniform_gridstate(ax.xaxis.minorTicks),
+                             _get_uniform_gridstate(ax.yaxis.minorTicks)]):
+        x_state = _get_uniform_gridstate(ax.xaxis.majorTicks)
+        y_state = _get_uniform_gridstate(ax.yaxis.majorTicks)
+        cycle = [(False, False), (True, False), (True, True), (False, True)]
+        try:
+            x_state, y_state = (
+                cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
+        except ValueError:
+            # Exclude major grids not in a uniform state.
+            pass
+        else:
+            # If turning major grids off, also turn minor grids off.
+            ax.grid(x_state, which="major" if x_state else "both", axis="x")
+            ax.grid(y_state, which="major" if y_state else "both", axis="y")
+            canvas.draw_idle()
+    # toggle major and minor grids in current axes (default key 'G')
+    if (event.key in grid_minor_keys
+            # Exclude major grids not in a uniform state.
+            and None not in [_get_uniform_gridstate(ax.xaxis.majorTicks),
+                             _get_uniform_gridstate(ax.yaxis.majorTicks)]):
+        x_state = _get_uniform_gridstate(ax.xaxis.minorTicks)
+        y_state = _get_uniform_gridstate(ax.yaxis.minorTicks)
+        cycle = [(False, False), (True, False), (True, True), (False, True)]
+        try:
+            x_state, y_state = (
+                cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
+        except ValueError:
+            # Exclude minor grids not in a uniform state.
+            pass
+        else:
+            ax.grid(x_state, which="both", axis="x")
+            ax.grid(y_state, which="both", axis="y")
+            canvas.draw_idle()
+    # toggle scaling of y-axes between 'log and 'linear' (default key 'l')
+    elif event.key in toggle_yscale_keys:
+        scale = ax.get_yscale()
+        if scale == 'log':
+            ax.set_yscale('linear')
+            ax.figure.canvas.draw_idle()
+        elif scale == 'linear':
+            try:
+                ax.set_yscale('log')
+            except ValueError as exc:
+                _log.warning(str(exc))
+                ax.set_yscale('linear')
+            ax.figure.canvas.draw_idle()
+    # toggle scaling of x-axes between 'log and 'linear' (default key 'k')
+    elif event.key in toggle_xscale_keys:
+        scalex = ax.get_xscale()
+        if scalex == 'log':
+            ax.set_xscale('linear')
+            ax.figure.canvas.draw_idle()
+        elif scalex == 'linear':
+            try:
+                ax.set_xscale('log')
+            except ValueError as exc:
+                _log.warning(str(exc))
+                ax.set_xscale('linear')
+            ax.figure.canvas.draw_idle()
+    # enable nagivation for all axes that contain the event (default key 'a')
+    elif event.key in all_keys:
+        for a in canvas.figure.get_axes():
+            if (event.x is not None and event.y is not None
+                    and a.in_axes(event)):  # FIXME: Why only these?
+                a.set_navigate(True)
+    # enable navigation only for axes with this index (if such an axes exist,
+    # otherwise do nothing)
+    elif event.key.isdigit() and event.key != '0':
+        n = int(event.key) - 1
+        if n < len(canvas.figure.get_axes()):
+            for i, a in enumerate(canvas.figure.get_axes()):
+                if (event.x is not None and event.y is not None
+                        and a.in_axes(event)):  # FIXME: Why only these?
+                    a.set_navigate(i == n)
+
+
+def button_press_handler(event, canvas, toolbar=None):
+    """
+    The default Matplotlib button actions for extra mouse buttons.
+    """
+    if toolbar is not None:
+        button_name = str(MouseButton(event.button))
+        if button_name in rcParams['keymap.back']:
+            toolbar.back()
+        elif button_name in rcParams['keymap.forward']:
+            toolbar.forward()
+
+
+class NonGuiException(Exception):
+    """Raised when trying show a figure in a non-GUI backend."""
+    pass
+
+
+class FigureManagerBase:
+    """
+    A backend-independent abstraction of a figure container and controller.
+
+    The figure manager is used by pyplot to interact with the window in a
+    backend-independent way. It's an adapter for the real (GUI) framework that
+    represents the visual figure on screen.
+
+    GUI backends define from this class to translate common operations such
+    as *show* or *resize* to the GUI-specific code. Non-GUI backends do not
+    support these operations an can just use the base class.
+
+    This following basic operations are accessible:
+
+    **Window operations**
+
+    - `~.FigureManagerBase.show`
+    - `~.FigureManagerBase.destroy`
+    - `~.FigureManagerBase.full_screen_toggle`
+    - `~.FigureManagerBase.resize`
+    - `~.FigureManagerBase.get_window_title`
+    - `~.FigureManagerBase.set_window_title`
+
+    **Key and mouse button press handling**
+
+    The figure manager sets up default key and mouse button press handling by
+    hooking up the `.key_press_handler` to the matplotlib event system. This
+    ensures the same shortcuts and mouse actions across backends.
+
+    **Other operations**
+
+    Subclasses will have additional attributes and functions to access
+    additional functionality. This is of course backend-specific. For example,
+    most GUI backends have ``window`` and ``toolbar`` attributes that give
+    access to the native GUI widgets of the respective framework.
+
+    Attributes
+    ----------
+    canvas : :class:`FigureCanvasBase`
+        The backend-specific canvas instance.
+
+    num : int or str
+        The figure number.
+
+    key_press_handler_id : int
+        The default key handler cid, when using the toolmanager.
+        To disable the default key press handling use::
+
+            figure.canvas.mpl_disconnect(
+                figure.canvas.manager.key_press_handler_id)
+
+    button_press_handler_id : int
+        The default mouse button handler cid, when using the toolmanager.
+        To disable the default button press handling use::
+
+            figure.canvas.mpl_disconnect(
+                figure.canvas.manager.button_press_handler_id)
+    """
+    def __init__(self, canvas, num):
+        self.canvas = canvas
+        canvas.manager = self  # store a pointer to parent
+        self.num = num
+
+        self.key_press_handler_id = None
+        self.button_press_handler_id = None
+        if rcParams['toolbar'] != 'toolmanager':
+            self.key_press_handler_id = self.canvas.mpl_connect(
+                'key_press_event',
+                self.key_press)
+            self.button_press_handler_id = self.canvas.mpl_connect(
+                'button_press_event',
+                self.button_press)
+
+        self.toolmanager = None
+        self.toolbar = None
+
+        @self.canvas.figure.add_axobserver
+        def notify_axes_change(fig):
+            # Called whenever the current axes is changed.
+            if self.toolmanager is None and self.toolbar is not None:
+                self.toolbar.update()
+
+    def show(self):
+        """
+        For GUI backends, show the figure window and redraw.
+        For non-GUI backends, raise an exception to be caught
+        by :meth:`~matplotlib.figure.Figure.show`, for an
+        optional warning.
+        """
+        raise NonGuiException()
+
+    def destroy(self):
+        pass
+
+    def full_screen_toggle(self):
+        pass
+
+    def resize(self, w, h):
+        """For GUI backends, resize the window (in pixels)."""
+
+    def key_press(self, event):
+        """
+        Implement the default Matplotlib key bindings defined at
+        :ref:`key-event-handling`.
+        """
+        if rcParams['toolbar'] != 'toolmanager':
+            key_press_handler(event, self.canvas, self.canvas.toolbar)
+
+    def button_press(self, event):
+        """The default Matplotlib button actions for extra mouse buttons."""
+        if rcParams['toolbar'] != 'toolmanager':
+            button_press_handler(event, self.canvas, self.canvas.toolbar)
+
+    def get_window_title(self):
+        """
+        Get the title text of the window containing the figure.
+
+        Return None for non-GUI (e.g., PS) backends.
+        """
+        return 'image'
+
+    def set_window_title(self, title):
+        """
+        Set the title text of the window containing the figure.
+
+        This has no effect for non-GUI (e.g., PS) backends.
+        """
+
+
+cursors = tools.cursors
+
+
+class NavigationToolbar2:
+    """
+    Base class for the navigation cursor, version 2
+
+    backends must implement a canvas that handles connections for
+    'button_press_event' and 'button_release_event'.  See
+    :meth:`FigureCanvasBase.mpl_connect` for more information
+
+
+    They must also define
+
+      :meth:`save_figure`
+         save the current figure
+
+      :meth:`set_cursor`
+         if you want the pointer icon to change
+
+      :meth:`_init_toolbar`
+         create your toolbar widget
+
+      :meth:`draw_rubberband` (optional)
+         draw the zoom to rect "rubberband" rectangle
+
+      :meth:`press`  (optional)
+         whenever a mouse button is pressed, you'll be notified with
+         the event
+
+      :meth:`release` (optional)
+         whenever a mouse button is released, you'll be notified with
+         the event
+
+      :meth:`set_message` (optional)
+         display message
+
+      :meth:`set_history_buttons` (optional)
+         you can change the history back / forward buttons to
+         indicate disabled / enabled state.
+
+    That's it, we'll do the rest!
+    """
+
+    # list of toolitems to add to the toolbar, format is:
+    # (
+    #   text, # the text of the button (often not visible to users)
+    #   tooltip_text, # the tooltip shown on hover (where possible)
+    #   image_file, # name of the image for the button (without the extension)
+    #   name_of_method, # name of the method in NavigationToolbar2 to call
+    # )
+    toolitems = (
+        ('Home', 'Reset original view', 'home', 'home'),
+        ('Back', 'Back to previous view', 'back', 'back'),
+        ('Forward', 'Forward to next view', 'forward', 'forward'),
+        (None, None, None, None),
+        ('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
+        ('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),
+        ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
+        (None, None, None, None),
+        ('Save', 'Save the figure', 'filesave', 'save_figure'),
+      )
+
+    def __init__(self, canvas):
+        self.canvas = canvas
+        canvas.toolbar = self
+        self._nav_stack = cbook.Stack()
+        self._xypress = None  # location and axis info at the time of the press
+        self._idPress = None
+        self._idRelease = None
+        self._active = None
+        # This cursor will be set after the initial draw.
+        self._lastCursor = cursors.POINTER
+        self._init_toolbar()
+        self._idDrag = self.canvas.mpl_connect(
+            'motion_notify_event', self.mouse_move)
+
+        self._ids_zoom = []
+        self._zoom_mode = None
+
+        self._button_pressed = None  # determined by button pressed at start
+
+        self.mode = ''  # a mode string for the status bar
+        self.set_history_buttons()
+
+    def set_message(self, s):
+        """Display a message on toolbar or in status bar."""
+
+    def back(self, *args):
+        """Move back up the view lim stack."""
+        self._nav_stack.back()
+        self.set_history_buttons()
+        self._update_view()
+
+    def draw_rubberband(self, event, x0, y0, x1, y1):
+        """
+        Draw a rectangle rubberband to indicate zoom limits.
+
+        Note that it is not guaranteed that ``x0 <= x1`` and ``y0 <= y1``.
+        """
+
+    def remove_rubberband(self):
+        """Remove the rubberband."""
+
+    def forward(self, *args):
+        """Move forward in the view lim stack."""
+        self._nav_stack.forward()
+        self.set_history_buttons()
+        self._update_view()
+
+    def home(self, *args):
+        """Restore the original view."""
+        self._nav_stack.home()
+        self.set_history_buttons()
+        self._update_view()
+
+    def _init_toolbar(self):
+        """
+        This is where you actually build the GUI widgets (called by
+        __init__).  The icons ``home.xpm``, ``back.xpm``, ``forward.xpm``,
+        ``hand.xpm``, ``zoom_to_rect.xpm`` and ``filesave.xpm`` are standard
+        across backends (there are ppm versions in CVS also).
+
+        You just need to set the callbacks
+
+        home         : self.home
+        back         : self.back
+        forward      : self.forward
+        hand         : self.pan
+        zoom_to_rect : self.zoom
+        filesave     : self.save_figure
+
+        You only need to define the last one - the others are in the base
+        class implementation.
+
+        """
+        raise NotImplementedError
+
+    def _update_cursor(self, event):
+        """
+        Update the cursor after a mouse move event or a tool (de)activation.
+        """
+        if not event.inaxes or not self._active:
+            if self._lastCursor != cursors.POINTER:
+                self.set_cursor(cursors.POINTER)
+                self._lastCursor = cursors.POINTER
+        else:
+            if (self._active == 'ZOOM'
+                    and self._lastCursor != cursors.SELECT_REGION):
+                self.set_cursor(cursors.SELECT_REGION)
+                self._lastCursor = cursors.SELECT_REGION
+            elif (self._active == 'PAN' and
+                  self._lastCursor != cursors.MOVE):
+                self.set_cursor(cursors.MOVE)
+                self._lastCursor = cursors.MOVE
+
+    @contextmanager
+    def _wait_cursor_for_draw_cm(self):
+        """
+        Set the cursor to a wait cursor when drawing the canvas.
+
+        In order to avoid constantly changing the cursor when the canvas
+        changes frequently, do nothing if this context was triggered during the
+        last second.  (Optimally we'd prefer only setting the wait cursor if
+        the *current* draw takes too long, but the current draw blocks the GUI
+        thread).
+        """
+        self._draw_time, last_draw_time = (
+            time.time(), getattr(self, "_draw_time", -np.inf))
+        if self._draw_time - last_draw_time > 1:
+            try:
+                self.set_cursor(cursors.WAIT)
+                yield
+            finally:
+                self.set_cursor(self._lastCursor)
+        else:
+            yield
+
+    def mouse_move(self, event):
+        self._update_cursor(event)
+
+        if event.inaxes and event.inaxes.get_navigate():
+
+            try:
+                s = event.inaxes.format_coord(event.xdata, event.ydata)
+            except (ValueError, OverflowError):
+                pass
+            else:
+                artists = [a for a in event.inaxes._mouseover_set
+                           if a.contains(event)[0] and a.get_visible()]
+
+                if artists:
+                    a = cbook._topmost_artist(artists)
+                    if a is not event.inaxes.patch:
+                        data = a.get_cursor_data(event)
+                        if data is not None:
+                            data_str = a.format_cursor_data(data)
+                            if data_str is not None:
+                                s = s + ' ' + data_str
+
+                if len(self.mode):
+                    self.set_message('%s, %s' % (self.mode, s))
+                else:
+                    self.set_message(s)
+        else:
+            self.set_message(self.mode)
+
+    def pan(self, *args):
+        """
+        Activate the pan/zoom tool.
+
+        Pan with left button, zoom with right.
+        """
+        # set the pointer icon and button press funcs to the
+        # appropriate callbacks
+
+        if self._active == 'PAN':
+            self._active = None
+        else:
+            self._active = 'PAN'
+        if self._idPress is not None:
+            self._idPress = self.canvas.mpl_disconnect(self._idPress)
+            self.mode = ''
+
+        if self._idRelease is not None:
+            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
+            self.mode = ''
+
+        if self._active:
+            self._idPress = self.canvas.mpl_connect(
+                'button_press_event', self.press_pan)
+            self._idRelease = self.canvas.mpl_connect(
+                'button_release_event', self.release_pan)
+            self.mode = 'pan/zoom'
+            self.canvas.widgetlock(self)
+        else:
+            self.canvas.widgetlock.release(self)
+
+        for a in self.canvas.figure.get_axes():
+            a.set_navigate_mode(self._active)
+
+        self.set_message(self.mode)
+
+    def press(self, event):
+        """Called whenever a mouse button is pressed."""
+
+    def press_pan(self, event):
+        """Callback for mouse button press in pan/zoom mode."""
+
+        if event.button == 1:
+            self._button_pressed = 1
+        elif event.button == 3:
+            self._button_pressed = 3
+        else:
+            self._button_pressed = None
+            return
+
+        if self._nav_stack() is None:
+            # set the home button to this view
+            self.push_current()
+
+        x, y = event.x, event.y
+        self._xypress = []
+        for i, a in enumerate(self.canvas.figure.get_axes()):
+            if (x is not None and y is not None and a.in_axes(event) and
+                    a.get_navigate() and a.can_pan()):
+                a.start_pan(x, y, event.button)
+                self._xypress.append((a, i))
+                self.canvas.mpl_disconnect(self._idDrag)
+                self._idDrag = self.canvas.mpl_connect('motion_notify_event',
+                                                       self.drag_pan)
+
+        self.press(event)
+
+    def press_zoom(self, event):
+        """Callback for mouse button press in zoom to rect mode."""
+        # If we're already in the middle of a zoom, pressing another
+        # button works to "cancel"
+        if self._ids_zoom != []:
+            for zoom_id in self._ids_zoom:
+                self.canvas.mpl_disconnect(zoom_id)
+            self.release(event)
+            self.draw()
+            self._xypress = None
+            self._button_pressed = None
+            self._ids_zoom = []
+            return
+
+        if event.button == 1:
+            self._button_pressed = 1
+        elif event.button == 3:
+            self._button_pressed = 3
+        else:
+            self._button_pressed = None
+            return
+
+        if self._nav_stack() is None:
+            # set the home button to this view
+            self.push_current()
+
+        x, y = event.x, event.y
+        self._xypress = []
+        for i, a in enumerate(self.canvas.figure.get_axes()):
+            if (x is not None and y is not None and a.in_axes(event) and
+                    a.get_navigate() and a.can_zoom()):
+                self._xypress.append((x, y, a, i, a._get_view()))
+
+        id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
+        id2 = self.canvas.mpl_connect('key_press_event',
+                                      self._switch_on_zoom_mode)
+        id3 = self.canvas.mpl_connect('key_release_event',
+                                      self._switch_off_zoom_mode)
+
+        self._ids_zoom = id1, id2, id3
+        self._zoom_mode = event.key
+
+        self.press(event)
+
+    def _switch_on_zoom_mode(self, event):
+        self._zoom_mode = event.key
+        self.mouse_move(event)
+
+    def _switch_off_zoom_mode(self, event):
+        self._zoom_mode = None
+        self.mouse_move(event)
+
+    def push_current(self):
+        """Push the current view limits and position onto the stack."""
+        self._nav_stack.push(
+            WeakKeyDictionary(
+                {ax: (ax._get_view(),
+                      # Store both the original and modified positions.
+                      (ax.get_position(True).frozen(),
+                       ax.get_position().frozen()))
+                 for ax in self.canvas.figure.axes}))
+        self.set_history_buttons()
+
+    def release(self, event):
+        """Callback for mouse button release."""
+
+    def release_pan(self, event):
+        """Callback for mouse button release in pan/zoom mode."""
+
+        if self._button_pressed is None:
+            return
+        self.canvas.mpl_disconnect(self._idDrag)
+        self._idDrag = self.canvas.mpl_connect(
+            'motion_notify_event', self.mouse_move)
+        for a, ind in self._xypress:
+            a.end_pan()
+        if not self._xypress:
+            return
+        self._xypress = []
+        self._button_pressed = None
+        self.push_current()
+        self.release(event)
+        self.draw()
+
+    def drag_pan(self, event):
+        """Callback for dragging in pan/zoom mode."""
+        for a, ind in self._xypress:
+            #safer to use the recorded button at the press than current button:
+            #multiple button can get pressed during motion...
+            a.drag_pan(self._button_pressed, event.key, event.x, event.y)
+        self.canvas.draw_idle()
+
+    def drag_zoom(self, event):
+        """Callback for dragging in zoom mode."""
+        if self._xypress:
+            x, y = event.x, event.y
+            lastx, lasty, a, ind, view = self._xypress[0]
+            (x1, y1), (x2, y2) = np.clip(
+                [[lastx, lasty], [x, y]], a.bbox.min, a.bbox.max)
+            if self._zoom_mode == "x":
+                y1, y2 = a.bbox.intervaly
+            elif self._zoom_mode == "y":
+                x1, x2 = a.bbox.intervalx
+            self.draw_rubberband(event, x1, y1, x2, y2)
+
+    def release_zoom(self, event):
+        """Callback for mouse button release in zoom to rect mode."""
+        for zoom_id in self._ids_zoom:
+            self.canvas.mpl_disconnect(zoom_id)
+        self._ids_zoom = []
+
+        self.remove_rubberband()
+
+        if not self._xypress:
+            return
+
+        last_a = []
+
+        for cur_xypress in self._xypress:
+            x, y = event.x, event.y
+            lastx, lasty, a, ind, view = cur_xypress
+            # ignore singular clicks - 5 pixels is a threshold
+            # allows the user to "cancel" a zoom action
+            # by zooming by less than 5 pixels
+            if ((abs(x - lastx) < 5 and self._zoom_mode != "y") or
+                    (abs(y - lasty) < 5 and self._zoom_mode != "x")):
+                self._xypress = None
+                self.release(event)
+                self.draw()
+                return
+
+            # detect twinx, twiny axes and avoid double zooming
+            twinx, twiny = False, False
+            if last_a:
+                for la in last_a:
+                    if a.get_shared_x_axes().joined(a, la):
+                        twinx = True
+                    if a.get_shared_y_axes().joined(a, la):
+                        twiny = True
+            last_a.append(a)
+
+            if self._button_pressed == 1:
+                direction = 'in'
+            elif self._button_pressed == 3:
+                direction = 'out'
+            else:
+                continue
+
+            a._set_view_from_bbox((lastx, lasty, x, y), direction,
+                                  self._zoom_mode, twinx, twiny)
+
+        self.draw()
+        self._xypress = None
+        self._button_pressed = None
+
+        self._zoom_mode = None
+
+        self.push_current()
+        self.release(event)
+
+    def draw(self):
+        """Redraw the canvases, update the locators."""
+        for a in self.canvas.figure.get_axes():
+            xaxis = getattr(a, 'xaxis', None)
+            yaxis = getattr(a, 'yaxis', None)
+            locators = []
+            if xaxis is not None:
+                locators.append(xaxis.get_major_locator())
+                locators.append(xaxis.get_minor_locator())
+            if yaxis is not None:
+                locators.append(yaxis.get_major_locator())
+                locators.append(yaxis.get_minor_locator())
+
+            for loc in locators:
+                loc.refresh()
+        self.canvas.draw_idle()
+
+    def _update_view(self):
+        """
+        Update the viewlim and position from the view and position stack for
+        each axes.
+        """
+        nav_info = self._nav_stack()
+        if nav_info is None:
+            return
+        # Retrieve all items at once to avoid any risk of GC deleting an Axes
+        # while in the middle of the loop below.
+        items = list(nav_info.items())
+        for ax, (view, (pos_orig, pos_active)) in items:
+            ax._set_view(view)
+            # Restore both the original and modified positions
+            ax._set_position(pos_orig, 'original')
+            ax._set_position(pos_active, 'active')
+        self.canvas.draw_idle()
+
+    def save_figure(self, *args):
+        """Save the current figure."""
+        raise NotImplementedError
+
+    def set_cursor(self, cursor):
+        """
+        Set the current cursor to one of the :class:`Cursors` enums values.
+
+        If required by the backend, this method should trigger an update in
+        the backend event loop after the cursor is set, as this method may be
+        called e.g. before a long-running task during which the GUI is not
+        updated.
+        """
+
+    def update(self):
+        """Reset the axes stack."""
+        self._nav_stack.clear()
+        self.set_history_buttons()
+
+    def zoom(self, *args):
+        """Activate zoom to rect mode."""
+        if self._active == 'ZOOM':
+            self._active = None
+        else:
+            self._active = 'ZOOM'
+
+        if self._idPress is not None:
+            self._idPress = self.canvas.mpl_disconnect(self._idPress)
+            self.mode = ''
+
+        if self._idRelease is not None:
+            self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
+            self.mode = ''
+
+        if self._active:
+            self._idPress = self.canvas.mpl_connect('button_press_event',
+                                                    self.press_zoom)
+            self._idRelease = self.canvas.mpl_connect('button_release_event',
+                                                      self.release_zoom)
+            self.mode = 'zoom rect'
+            self.canvas.widgetlock(self)
+        else:
+            self.canvas.widgetlock.release(self)
+
+        for a in self.canvas.figure.get_axes():
+            a.set_navigate_mode(self._active)
+
+        self.set_message(self.mode)
+
+    def set_history_buttons(self):
+        """Enable or disable the back/forward button."""
+
+
+class ToolContainerBase:
+    """
+    Base class for all tool containers, e.g. toolbars.
+
+    Attributes
+    ----------
+    toolmanager : `ToolManager`
+        The tools with which this `ToolContainer` wants to communicate.
+    """
+
+    _icon_extension = '.png'
+    """
+    Toolcontainer button icon image format extension
+
+    **String**: Image extension
+    """
+
+    def __init__(self, toolmanager):
+        self.toolmanager = toolmanager
+        self.toolmanager.toolmanager_connect('tool_removed_event',
+                                             self._remove_tool_cbk)
+
+    def _tool_toggled_cbk(self, event):
+        """
+        Captures the 'tool_trigger_[name]'
+
+        This only gets used for toggled tools
+        """
+        self.toggle_toolitem(event.tool.name, event.tool.toggled)
+
+    def add_tool(self, tool, group, position=-1):
+        """
+        Adds a tool to this container
+
+        Parameters
+        ----------
+        tool : tool_like
+            The tool to add, see `ToolManager.get_tool`.
+        group : str
+            The name of the group to add this tool to.
+        position : int (optional)
+            The position within the group to place this tool.  Defaults to end.
+        """
+        tool = self.toolmanager.get_tool(tool)
+        image = self._get_image_filename(tool.image)
+        toggle = getattr(tool, 'toggled', None) is not None
+        self.add_toolitem(tool.name, group, position,
+                          image, tool.description, toggle)
+        if toggle:
+            self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name,
+                                                 self._tool_toggled_cbk)
+            # If initially toggled
+            if tool.toggled:
+                self.toggle_toolitem(tool.name, True)
+
+    def _remove_tool_cbk(self, event):
+        """Captures the 'tool_removed_event' signal and removes the tool."""
+        self.remove_toolitem(event.tool.name)
+
+    def _get_image_filename(self, image):
+        """Find the image based on its name."""
+        if not image:
+            return None
+
+        basedir = cbook._get_data_path("images")
+        for fname in [
+            image,
+            image + self._icon_extension,
+            str(basedir / image),
+            str(basedir / (image + self._icon_extension)),
+        ]:
+            if os.path.isfile(fname):
+                return fname
+
+    def trigger_tool(self, name):
+        """
+        Trigger the tool
+
+        Parameters
+        ----------
+        name : str
+            Name (id) of the tool triggered from within the container.
+        """
+        self.toolmanager.trigger_tool(name, sender=self)
+
+    def add_toolitem(self, name, group, position, image, description, toggle):
+        """
+        Add a toolitem to the container
+
+        This method must get implemented per backend
+
+        The callback associated with the button click event,
+        must be **EXACTLY** `self.trigger_tool(name)`
+
+        Parameters
+        ----------
+        name : str
+            Name of the tool to add, this gets used as the tool's ID and as the
+            default label of the buttons
+        group : String
+            Name of the group that this tool belongs to
+        position : Int
+            Position of the tool within its group, if -1 it goes at the End
+        image_file : String
+            Filename of the image for the button or `None`
+        description : String
+            Description of the tool, used for the tooltips
+        toggle : Bool
+            * `True` : The button is a toggle (change the pressed/unpressed
+              state between consecutive clicks)
+            * `False` : The button is a normal button (returns to unpressed
+              state after release)
+        """
+        raise NotImplementedError
+
+    def toggle_toolitem(self, name, toggled):
+        """
+        Toggle the toolitem without firing event
+
+        Parameters
+        ----------
+        name : String
+            Id of the tool to toggle
+        toggled : bool
+            Whether to set this tool as toggled or not.
+        """
+        raise NotImplementedError
+
+    def remove_toolitem(self, name):
+        """
+        Remove a toolitem from the `ToolContainer`.
+
+        This method must get implemented per backend.
+
+        Called when `ToolManager` emits a `tool_removed_event`.
+
+        Parameters
+        ----------
+        name : str
+            Name of the tool to remove.
+        """
+        raise NotImplementedError
+
+
+class StatusbarBase:
+    """Base class for the statusbar."""
+    def __init__(self, toolmanager):
+        self.toolmanager = toolmanager
+        self.toolmanager.toolmanager_connect('tool_message_event',
+                                             self._message_cbk)
+
+    def _message_cbk(self, event):
+        """Capture the 'tool_message_event' and set the message."""
+        self.set_message(event.message)
+
+    def set_message(self, s):
+        """
+        Display a message on toolbar or in status bar.
+
+        Parameters
+        ----------
+        s : str
+            Message text.
+        """
+        pass
+
+
+class _Backend:
+    # A backend can be defined by using the following pattern:
+    #
+    # @_Backend.export
+    # class FooBackend(_Backend):
+    #     # override the attributes and methods documented below.
+
+    # `backend_version` may be overridden by the subclass.
+    backend_version = "unknown"
+
+    # The `FigureCanvas` class must be defined.
+    FigureCanvas = None
+
+    # For interactive backends, the `FigureManager` class must be overridden.
+    FigureManager = FigureManagerBase
+
+    # The following methods must be left as None for non-interactive backends.
+    # For interactive backends, `trigger_manager_draw` should be a function
+    # taking a manager as argument and triggering a canvas draw, and `mainloop`
+    # should be a function taking no argument and starting the backend main
+    # loop.
+    trigger_manager_draw = None
+    mainloop = None
+
+    # The following methods will be automatically defined and exported, but
+    # can be overridden.
+
+    @classmethod
+    def new_figure_manager(cls, num, *args, **kwargs):
+        """Create a new figure manager instance."""
+        # This import needs to happen here due to circular imports.
+        from matplotlib.figure import Figure
+        fig_cls = kwargs.pop('FigureClass', Figure)
+        fig = fig_cls(*args, **kwargs)
+        return cls.new_figure_manager_given_figure(num, fig)
+
+    @classmethod
+    def new_figure_manager_given_figure(cls, num, figure):
+        """Create a new figure manager instance for the given figure."""
+        canvas = cls.FigureCanvas(figure)
+        manager = cls.FigureManager(canvas, num)
+        return manager
+
+    @classmethod
+    def draw_if_interactive(cls):
+        if cls.trigger_manager_draw is not None and is_interactive():
+            manager = Gcf.get_active()
+            if manager:
+                cls.trigger_manager_draw(manager)
+
+    @classmethod
+    @cbook._make_keyword_only("3.1", "block")
+    def show(cls, block=None):
+        """
+        Show all figures.
+
+        `show` blocks by calling `mainloop` if *block* is ``True``, or if it
+        is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
+        `interactive` mode.
+        """
+        managers = Gcf.get_all_fig_managers()
+        if not managers:
+            return
+        for manager in managers:
+            # Emits a warning if the backend is non-interactive.
+            manager.canvas.figure.show()
+        if cls.mainloop is None:
+            return
+        if block is None:
+            # Hack: Are we in IPython's pylab mode?
+            from matplotlib import pyplot
+            try:
+                # IPython versions >= 0.10 tack the _needmain attribute onto
+                # pyplot.show, and always set it to False, when in %pylab mode.
+                ipython_pylab = not pyplot.show._needmain
+            except AttributeError:
+                ipython_pylab = False
+            block = not ipython_pylab and not is_interactive()
+            # TODO: The above is a hack to get the WebAgg backend working with
+            # ipython's `%pylab` mode until proper integration is implemented.
+            if get_backend() == "WebAgg":
+                block = True
+        if block:
+            cls.mainloop()
+
+    # This method is the one actually exporting the required methods.
+
+    @staticmethod
+    def export(cls):
+        for name in [
+                "backend_version",
+                "FigureCanvas",
+                "FigureManager",
+                "new_figure_manager",
+                "new_figure_manager_given_figure",
+                "draw_if_interactive",
+                "show",
+        ]:
+            setattr(sys.modules[cls.__module__], name, getattr(cls, name))
+
+        # For back-compatibility, generate a shim `Show` class.
+
+        class Show(ShowBase):
+            def mainloop(self):
+                return cls.mainloop()
+
+        setattr(sys.modules[cls.__module__], "Show", Show)
+        return cls
+
+
+class ShowBase(_Backend):
+    """
+    Simple base class to generate a ``show()`` function in backends.
+
+    Subclass must override ``mainloop()`` method.
+    """
+
+    def __call__(self, block=None):
+        return self.show(block=block)

+ 423 - 0
venv/lib/python3.8/site-packages/matplotlib/backend_managers.py

@@ -0,0 +1,423 @@
+import logging
+
+import matplotlib.cbook as cbook
+import matplotlib.widgets as widgets
+from matplotlib.rcsetup import validate_stringlist
+import matplotlib.backend_tools as tools
+
+_log = logging.getLogger(__name__)
+
+
+class ToolEvent:
+    """Event for tool manipulation (add/remove)."""
+    def __init__(self, name, sender, tool, data=None):
+        self.name = name
+        self.sender = sender
+        self.tool = tool
+        self.data = data
+
+
+class ToolTriggerEvent(ToolEvent):
+    """Event to inform that a tool has been triggered."""
+    def __init__(self, name, sender, tool, canvasevent=None, data=None):
+        ToolEvent.__init__(self, name, sender, tool, data)
+        self.canvasevent = canvasevent
+
+
+class ToolManagerMessageEvent:
+    """
+    Event carrying messages from toolmanager.
+
+    Messages usually get displayed to the user by the toolbar.
+    """
+    def __init__(self, name, sender, message):
+        self.name = name
+        self.sender = sender
+        self.message = message
+
+
+class ToolManager:
+    """
+    Manager for actions triggered by user interactions (key press, toolbar
+    clicks, ...) on a Figure.
+
+    Attributes
+    ----------
+    figure : `Figure`
+    keypresslock : `widgets.LockDraw`
+        `LockDraw` object to know if the `canvas` key_press_event is locked
+    messagelock : `widgets.LockDraw`
+        `LockDraw` object to know if the message is available to write
+    """
+
+    def __init__(self, figure=None):
+        _log.warning('Treat the new Tool classes introduced in v1.5 as '
+                     'experimental for now, the API will likely change in '
+                     'version 2.1 and perhaps the rcParam as well')
+
+        self._key_press_handler_id = None
+
+        self._tools = {}
+        self._keys = {}
+        self._toggled = {}
+        self._callbacks = cbook.CallbackRegistry()
+
+        # to process keypress event
+        self.keypresslock = widgets.LockDraw()
+        self.messagelock = widgets.LockDraw()
+
+        self._figure = None
+        self.set_figure(figure)
+
+    @property
+    def canvas(self):
+        """Canvas managed by FigureManager."""
+        if not self._figure:
+            return None
+        return self._figure.canvas
+
+    @property
+    def figure(self):
+        """Figure that holds the canvas."""
+        return self._figure
+
+    @figure.setter
+    def figure(self, figure):
+        self.set_figure(figure)
+
+    def set_figure(self, figure, update_tools=True):
+        """
+        Bind the given figure to the tools.
+
+        Parameters
+        ----------
+        figure : `.Figure`
+        update_tools : bool
+            Force tools to update figure
+        """
+        if self._key_press_handler_id:
+            self.canvas.mpl_disconnect(self._key_press_handler_id)
+        self._figure = figure
+        if figure:
+            self._key_press_handler_id = self.canvas.mpl_connect(
+                'key_press_event', self._key_press)
+        if update_tools:
+            for tool in self._tools.values():
+                tool.figure = figure
+
+    def toolmanager_connect(self, s, func):
+        """
+        Connect event with string *s* to *func*.
+
+        Parameters
+        ----------
+        s : String
+            Name of the event
+
+            The following events are recognized
+
+            - 'tool_message_event'
+            - 'tool_removed_event'
+            - 'tool_added_event'
+
+            For every tool added a new event is created
+
+            - 'tool_trigger_TOOLNAME`
+              Where TOOLNAME is the id of the tool.
+
+        func : function
+            Function to be called with signature
+            def func(event)
+        """
+        return self._callbacks.connect(s, func)
+
+    def toolmanager_disconnect(self, cid):
+        """
+        Disconnect callback id *cid*.
+
+        Example usage::
+
+            cid = toolmanager.toolmanager_connect('tool_trigger_zoom', onpress)
+            #...later
+            toolmanager.toolmanager_disconnect(cid)
+        """
+        return self._callbacks.disconnect(cid)
+
+    def message_event(self, message, sender=None):
+        """Emit a `ToolManagerMessageEvent`."""
+        if sender is None:
+            sender = self
+
+        s = 'tool_message_event'
+        event = ToolManagerMessageEvent(s, sender, message)
+        self._callbacks.process(s, event)
+
+    @property
+    def active_toggle(self):
+        """Currently toggled tools."""
+        return self._toggled
+
+    def get_tool_keymap(self, name):
+        """
+        Get the keymap associated with the specified tool.
+
+        Parameters
+        ----------
+        name : str
+            Name of the Tool.
+
+        Returns
+        -------
+        list : list of keys associated with the Tool
+        """
+
+        keys = [k for k, i in self._keys.items() if i == name]
+        return keys
+
+    def _remove_keys(self, name):
+        for k in self.get_tool_keymap(name):
+            del self._keys[k]
+
+    def update_keymap(self, name, *keys):
+        """
+        Set the keymap to associate with the specified tool.
+
+        Parameters
+        ----------
+        name : str
+            Name of the Tool.
+        keys : keys to associate with the Tool
+        """
+
+        if name not in self._tools:
+            raise KeyError('%s not in Tools' % name)
+
+        self._remove_keys(name)
+
+        for key in keys:
+            for k in validate_stringlist(key):
+                if k in self._keys:
+                    cbook._warn_external('Key %s changed from %s to %s' %
+                                         (k, self._keys[k], name))
+                self._keys[k] = name
+
+    def remove_tool(self, name):
+        """
+        Remove tool named *name*.
+
+        Parameters
+        ----------
+        name : str
+            Name of the Tool.
+        """
+
+        tool = self.get_tool(name)
+        tool.destroy()
+
+        # If is a toggle tool and toggled, untoggle
+        if getattr(tool, 'toggled', False):
+            self.trigger_tool(tool, 'toolmanager')
+
+        self._remove_keys(name)
+
+        s = 'tool_removed_event'
+        event = ToolEvent(s, self, tool)
+        self._callbacks.process(s, event)
+
+        del self._tools[name]
+
+    def add_tool(self, name, tool, *args, **kwargs):
+        """
+        Add *tool* to `ToolManager`.
+
+        If successful, adds a new event ``tool_trigger_{name}`` where
+        ``{name}`` is the *name* of the tool; the event is fired everytime the
+        tool is triggered.
+
+        Parameters
+        ----------
+        name : str
+            Name of the tool, treated as the ID, has to be unique.
+        tool : class_like, i.e. str or type
+            Reference to find the class of the Tool to added.
+
+        Notes
+        -----
+        args and kwargs get passed directly to the tools constructor.
+
+        See Also
+        --------
+        matplotlib.backend_tools.ToolBase : The base class for tools.
+        """
+
+        tool_cls = self._get_cls_to_instantiate(tool)
+        if not tool_cls:
+            raise ValueError('Impossible to find class for %s' % str(tool))
+
+        if name in self._tools:
+            cbook._warn_external('A "Tool class" with the same name already '
+                                 'exists, not added')
+            return self._tools[name]
+
+        tool_obj = tool_cls(self, name, *args, **kwargs)
+        self._tools[name] = tool_obj
+
+        if tool_cls.default_keymap is not None:
+            self.update_keymap(name, tool_cls.default_keymap)
+
+        # For toggle tools init the radio_group in self._toggled
+        if isinstance(tool_obj, tools.ToolToggleBase):
+            # None group is not mutually exclusive, a set is used to keep track
+            # of all toggled tools in this group
+            if tool_obj.radio_group is None:
+                self._toggled.setdefault(None, set())
+            else:
+                self._toggled.setdefault(tool_obj.radio_group, None)
+
+            # If initially toggled
+            if tool_obj.toggled:
+                self._handle_toggle(tool_obj, None, None, None)
+        tool_obj.set_figure(self.figure)
+
+        self._tool_added_event(tool_obj)
+        return tool_obj
+
+    def _tool_added_event(self, tool):
+        s = 'tool_added_event'
+        event = ToolEvent(s, self, tool)
+        self._callbacks.process(s, event)
+
+    def _handle_toggle(self, tool, sender, canvasevent, data):
+        """
+        Toggle tools, need to untoggle prior to using other Toggle tool.
+        Called from trigger_tool.
+
+        Parameters
+        ----------
+        tool : Tool object
+        sender : object
+            Object that wishes to trigger the tool
+        canvasevent : Event
+            Original Canvas event or None
+        data : Object
+            Extra data to pass to the tool when triggering
+        """
+
+        radio_group = tool.radio_group
+        # radio_group None is not mutually exclusive
+        # just keep track of toggled tools in this group
+        if radio_group is None:
+            if tool.name in self._toggled[None]:
+                self._toggled[None].remove(tool.name)
+            else:
+                self._toggled[None].add(tool.name)
+            return
+
+        # If the tool already has a toggled state, untoggle it
+        if self._toggled[radio_group] == tool.name:
+            toggled = None
+        # If no tool was toggled in the radio_group
+        # toggle it
+        elif self._toggled[radio_group] is None:
+            toggled = tool.name
+        # Other tool in the radio_group is toggled
+        else:
+            # Untoggle previously toggled tool
+            self.trigger_tool(self._toggled[radio_group],
+                              self,
+                              canvasevent,
+                              data)
+            toggled = tool.name
+
+        # Keep track of the toggled tool in the radio_group
+        self._toggled[radio_group] = toggled
+
+    def _get_cls_to_instantiate(self, callback_class):
+        # Find the class that corresponds to the tool
+        if isinstance(callback_class, str):
+            # FIXME: make more complete searching structure
+            if callback_class in globals():
+                callback_class = globals()[callback_class]
+            else:
+                mod = 'backend_tools'
+                current_module = __import__(mod,
+                                            globals(), locals(), [mod], 1)
+
+                callback_class = getattr(current_module, callback_class, False)
+        if callable(callback_class):
+            return callback_class
+        else:
+            return None
+
+    def trigger_tool(self, name, sender=None, canvasevent=None, data=None):
+        """
+        Trigger a tool and emit the ``tool_trigger_{name}`` event.
+
+        Parameters
+        ----------
+        name : str
+            Name of the tool.
+        sender : object
+            Object that wishes to trigger the tool
+        canvasevent : Event
+            Original Canvas event or None
+        data : Object
+            Extra data to pass to the tool when triggering
+        """
+        tool = self.get_tool(name)
+        if tool is None:
+            return
+
+        if sender is None:
+            sender = self
+
+        self._trigger_tool(name, sender, canvasevent, data)
+
+        s = 'tool_trigger_%s' % name
+        event = ToolTriggerEvent(s, sender, tool, canvasevent, data)
+        self._callbacks.process(s, event)
+
+    def _trigger_tool(self, name, sender=None, canvasevent=None, data=None):
+        """Actually trigger a tool."""
+        tool = self.get_tool(name)
+
+        if isinstance(tool, tools.ToolToggleBase):
+            self._handle_toggle(tool, sender, canvasevent, data)
+
+        # Important!!!
+        # This is where the Tool object gets triggered
+        tool.trigger(sender, canvasevent, data)
+
+    def _key_press(self, event):
+        if event.key is None or self.keypresslock.locked():
+            return
+
+        name = self._keys.get(event.key, None)
+        if name is None:
+            return
+        self.trigger_tool(name, canvasevent=event)
+
+    @property
+    def tools(self):
+        """A dict mapping tool name -> controlled tool."""
+        return self._tools
+
+    def get_tool(self, name, warn=True):
+        """
+        Return the tool object, also accepts the actual tool for convenience.
+
+        Parameters
+        ----------
+        name : str, ToolBase
+            Name of the tool, or the tool itself
+        warn : bool, optional
+            If this method should give warnings.
+        """
+        if isinstance(name, tools.ToolBase) and name.name in self._tools:
+            return name
+        if name not in self._tools:
+            if warn:
+                cbook._warn_external("ToolManager does not control tool "
+                                     "%s" % name)
+            return None
+        return self._tools[name]

+ 1146 - 0
venv/lib/python3.8/site-packages/matplotlib/backend_tools.py

@@ -0,0 +1,1146 @@
+"""
+Abstract base classes define the primitives for Tools.
+These tools are used by `matplotlib.backend_managers.ToolManager`
+
+:class:`ToolBase`
+    Simple stateless tool
+
+:class:`ToolToggleBase`
+    Tool that has two states, only one Toggle tool can be
+    active at any given time for the same
+    `matplotlib.backend_managers.ToolManager`
+"""
+
+from enum import IntEnum
+import logging
+import re
+import time
+from types import SimpleNamespace
+from weakref import WeakKeyDictionary
+
+import numpy as np
+
+from matplotlib import rcParams
+from matplotlib._pylab_helpers import Gcf
+import matplotlib.cbook as cbook
+
+_log = logging.getLogger(__name__)
+
+
+class Cursors(IntEnum):  # Must subclass int for the macOS backend.
+    """Backend-independent cursor types."""
+    HAND, POINTER, SELECT_REGION, MOVE, WAIT = range(5)
+cursors = Cursors  # Backcompat.
+
+# Views positions tool
+_views_positions = 'viewpos'
+
+
+class ToolBase:
+    """
+    Base tool class
+
+    A base tool, only implements `trigger` method or not method at all.
+    The tool is instantiated by `matplotlib.backend_managers.ToolManager`
+
+    Attributes
+    ----------
+    toolmanager : `matplotlib.backend_managers.ToolManager`
+        ToolManager that controls this Tool
+    figure : `FigureCanvas`
+        Figure instance that is affected by this Tool
+    name : str
+        Used as **Id** of the tool, has to be unique among tools of the same
+        ToolManager
+    """
+
+    default_keymap = None
+    """
+    Keymap to associate with this tool
+
+    **String**: List of comma separated keys that will be used to call this
+    tool when the keypress event of *self.figure.canvas* is emitted
+    """
+
+    description = None
+    """
+    Description of the Tool
+
+    **String**: If the Tool is included in the Toolbar this text is used
+    as a Tooltip
+    """
+
+    image = None
+    """
+    Filename of the image
+
+    **String**: Filename of the image to use in the toolbar. If None, the
+    *name* is used as a label in the toolbar button
+    """
+
+    def __init__(self, toolmanager, name):
+        cbook._warn_external(
+            'The new Tool classes introduced in v1.5 are experimental; their '
+            'API (including names) will likely change in future versions.')
+        self._name = name
+        self._toolmanager = toolmanager
+        self._figure = None
+
+    @property
+    def figure(self):
+        return self._figure
+
+    @figure.setter
+    def figure(self, figure):
+        self.set_figure(figure)
+
+    @property
+    def canvas(self):
+        if not self._figure:
+            return None
+        return self._figure.canvas
+
+    @property
+    def toolmanager(self):
+        return self._toolmanager
+
+    def _make_classic_style_pseudo_toolbar(self):
+        """
+        Return a placeholder object with a single `canvas` attribute.
+
+        This is useful to reuse the implementations of tools already provided
+        by the classic Toolbars.
+        """
+        return SimpleNamespace(canvas=self.canvas)
+
+    def set_figure(self, figure):
+        """
+        Assign a figure to the tool
+
+        Parameters
+        ----------
+        figure : `Figure`
+        """
+        self._figure = figure
+
+    def trigger(self, sender, event, data=None):
+        """
+        Called when this tool gets used
+
+        This method is called by
+        `matplotlib.backend_managers.ToolManager.trigger_tool`
+
+        Parameters
+        ----------
+        event : `Event`
+            The Canvas event that caused this tool to be called
+        sender : object
+            Object that requested the tool to be triggered
+        data : object
+            Extra data
+        """
+
+        pass
+
+    @property
+    def name(self):
+        """Tool Id"""
+        return self._name
+
+    def destroy(self):
+        """
+        Destroy the tool
+
+        This method is called when the tool is removed by
+        `matplotlib.backend_managers.ToolManager.remove_tool`
+        """
+        pass
+
+
+class ToolToggleBase(ToolBase):
+    """
+    Toggleable tool
+
+    Every time it is triggered, it switches between enable and disable
+
+    Parameters
+    ----------
+    ``*args``
+        Variable length argument to be used by the Tool
+    ``**kwargs``
+        `toggled` if present and True, sets the initial state of the Tool
+        Arbitrary keyword arguments to be consumed by the Tool
+    """
+
+    radio_group = None
+    """Attribute to group 'radio' like tools (mutually exclusive)
+
+    **String** that identifies the group or **None** if not belonging to a
+    group
+    """
+
+    cursor = None
+    """Cursor to use when the tool is active"""
+
+    default_toggled = False
+    """Default of toggled state"""
+
+    def __init__(self, *args, **kwargs):
+        self._toggled = kwargs.pop('toggled', self.default_toggled)
+        ToolBase.__init__(self, *args, **kwargs)
+
+    def trigger(self, sender, event, data=None):
+        """Calls `enable` or `disable` based on `toggled` value"""
+        if self._toggled:
+            self.disable(event)
+        else:
+            self.enable(event)
+        self._toggled = not self._toggled
+
+    def enable(self, event=None):
+        """
+        Enable the toggle tool
+
+        `trigger` calls this method when `toggled` is False
+        """
+        pass
+
+    def disable(self, event=None):
+        """
+        Disable the toggle tool
+
+        `trigger` call this method when `toggled` is True.
+
+        This can happen in different circumstances
+
+        * Click on the toolbar tool button
+        * Call to `matplotlib.backend_managers.ToolManager.trigger_tool`
+        * Another `ToolToggleBase` derived tool is triggered
+          (from the same `ToolManager`)
+        """
+        pass
+
+    @property
+    def toggled(self):
+        """State of the toggled tool"""
+
+        return self._toggled
+
+    def set_figure(self, figure):
+        toggled = self.toggled
+        if toggled:
+            if self.figure:
+                self.trigger(self, None)
+            else:
+                # if no figure the internal state is not changed
+                # we change it here so next call to trigger will change it back
+                self._toggled = False
+        ToolBase.set_figure(self, figure)
+        if toggled:
+            if figure:
+                self.trigger(self, None)
+            else:
+                # if there is no figure, trigger won't change the internal
+                # state we change it back
+                self._toggled = True
+
+
+class SetCursorBase(ToolBase):
+    """
+    Change to the current cursor while inaxes
+
+    This tool, keeps track of all `ToolToggleBase` derived tools, and calls
+    set_cursor when a tool gets triggered
+    """
+    def __init__(self, *args, **kwargs):
+        ToolBase.__init__(self, *args, **kwargs)
+        self._idDrag = None
+        self._cursor = None
+        self._default_cursor = cursors.POINTER
+        self._last_cursor = self._default_cursor
+        self.toolmanager.toolmanager_connect('tool_added_event',
+                                             self._add_tool_cbk)
+
+        # process current tools
+        for tool in self.toolmanager.tools.values():
+            self._add_tool(tool)
+
+    def set_figure(self, figure):
+        if self._idDrag:
+            self.canvas.mpl_disconnect(self._idDrag)
+        ToolBase.set_figure(self, figure)
+        if figure:
+            self._idDrag = self.canvas.mpl_connect(
+                'motion_notify_event', self._set_cursor_cbk)
+
+    def _tool_trigger_cbk(self, event):
+        if event.tool.toggled:
+            self._cursor = event.tool.cursor
+        else:
+            self._cursor = None
+
+        self._set_cursor_cbk(event.canvasevent)
+
+    def _add_tool(self, tool):
+        """Set the cursor when the tool is triggered."""
+        if getattr(tool, 'cursor', None) is not None:
+            self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name,
+                                                 self._tool_trigger_cbk)
+
+    def _add_tool_cbk(self, event):
+        """Process every newly added tool."""
+        if event.tool is self:
+            return
+        self._add_tool(event.tool)
+
+    def _set_cursor_cbk(self, event):
+        if not event:
+            return
+
+        if not getattr(event, 'inaxes', False) or not self._cursor:
+            if self._last_cursor != self._default_cursor:
+                self.set_cursor(self._default_cursor)
+                self._last_cursor = self._default_cursor
+        elif self._cursor:
+            cursor = self._cursor
+            if cursor and self._last_cursor != cursor:
+                self.set_cursor(cursor)
+                self._last_cursor = cursor
+
+    def set_cursor(self, cursor):
+        """
+        Set the cursor
+
+        This method has to be implemented per backend
+        """
+        raise NotImplementedError
+
+
+class ToolCursorPosition(ToolBase):
+    """
+    Send message with the current pointer position
+
+    This tool runs in the background reporting the position of the cursor
+    """
+    def __init__(self, *args, **kwargs):
+        self._idDrag = None
+        ToolBase.__init__(self, *args, **kwargs)
+
+    def set_figure(self, figure):
+        if self._idDrag:
+            self.canvas.mpl_disconnect(self._idDrag)
+        ToolBase.set_figure(self, figure)
+        if figure:
+            self._idDrag = self.canvas.mpl_connect(
+                'motion_notify_event', self.send_message)
+
+    def send_message(self, event):
+        """Call `matplotlib.backend_managers.ToolManager.message_event`"""
+        if self.toolmanager.messagelock.locked():
+            return
+
+        message = ' '
+
+        if event.inaxes and event.inaxes.get_navigate():
+            try:
+                s = event.inaxes.format_coord(event.xdata, event.ydata)
+            except (ValueError, OverflowError):
+                pass
+            else:
+                artists = [a for a in event.inaxes._mouseover_set
+                           if a.contains(event) and a.get_visible()]
+
+                if artists:
+                    a = cbook._topmost_artist(artists)
+                    if a is not event.inaxes.patch:
+                        data = a.get_cursor_data(event)
+                        if data is not None:
+                            data_str = a.format_cursor_data(data)
+                            if data_str is not None:
+                                s = s + ' ' + data_str
+
+                message = s
+        self.toolmanager.message_event(message, self)
+
+
+class RubberbandBase(ToolBase):
+    """Draw and remove rubberband"""
+    def trigger(self, sender, event, data):
+        """Call `draw_rubberband` or `remove_rubberband` based on data"""
+        if not self.figure.canvas.widgetlock.available(sender):
+            return
+        if data is not None:
+            self.draw_rubberband(*data)
+        else:
+            self.remove_rubberband()
+
+    def draw_rubberband(self, *data):
+        """
+        Draw rubberband
+
+        This method must get implemented per backend
+        """
+        raise NotImplementedError
+
+    def remove_rubberband(self):
+        """
+        Remove rubberband
+
+        This method should get implemented per backend
+        """
+        pass
+
+
+class ToolQuit(ToolBase):
+    """Tool to call the figure manager destroy method"""
+
+    description = 'Quit the figure'
+    default_keymap = rcParams['keymap.quit']
+
+    def trigger(self, sender, event, data=None):
+        Gcf.destroy_fig(self.figure)
+
+
+class ToolQuitAll(ToolBase):
+    """Tool to call the figure manager destroy method"""
+
+    description = 'Quit all figures'
+    default_keymap = rcParams['keymap.quit_all']
+
+    def trigger(self, sender, event, data=None):
+        Gcf.destroy_all()
+
+
+class ToolEnableAllNavigation(ToolBase):
+    """Tool to enable all axes for toolmanager interaction"""
+
+    description = 'Enable all axes toolmanager'
+    default_keymap = rcParams['keymap.all_axes']
+
+    def trigger(self, sender, event, data=None):
+        if event.inaxes is None:
+            return
+
+        for a in self.figure.get_axes():
+            if (event.x is not None and event.y is not None
+                    and a.in_axes(event)):
+                a.set_navigate(True)
+
+
+class ToolEnableNavigation(ToolBase):
+    """Tool to enable a specific axes for toolmanager interaction"""
+
+    description = 'Enable one axes toolmanager'
+    default_keymap = (1, 2, 3, 4, 5, 6, 7, 8, 9)
+
+    def trigger(self, sender, event, data=None):
+        if event.inaxes is None:
+            return
+
+        n = int(event.key) - 1
+        if n < len(self.figure.get_axes()):
+            for i, a in enumerate(self.figure.get_axes()):
+                if (event.x is not None and event.y is not None
+                        and a.in_axes(event)):
+                    a.set_navigate(i == n)
+
+
+class _ToolGridBase(ToolBase):
+    """Common functionality between ToolGrid and ToolMinorGrid."""
+
+    _cycle = [(False, False), (True, False), (True, True), (False, True)]
+
+    def trigger(self, sender, event, data=None):
+        ax = event.inaxes
+        if ax is None:
+            return
+        try:
+            x_state, x_which, y_state, y_which = self._get_next_grid_states(ax)
+        except ValueError:
+            pass
+        else:
+            ax.grid(x_state, which=x_which, axis="x")
+            ax.grid(y_state, which=y_which, axis="y")
+            ax.figure.canvas.draw_idle()
+
+    @staticmethod
+    def _get_uniform_grid_state(ticks):
+        """
+        Check whether all grid lines are in the same visibility state.
+
+        Returns True/False if all grid lines are on or off, None if they are
+        not all in the same state.
+        """
+        if all(tick.gridline.get_visible() for tick in ticks):
+            return True
+        elif not any(tick.gridline.get_visible() for tick in ticks):
+            return False
+        else:
+            return None
+
+
+class ToolGrid(_ToolGridBase):
+    """Tool to toggle the major grids of the figure"""
+
+    description = 'Toggle major grids'
+    default_keymap = rcParams['keymap.grid']
+
+    def _get_next_grid_states(self, ax):
+        if None in map(self._get_uniform_grid_state,
+                       [ax.xaxis.minorTicks, ax.yaxis.minorTicks]):
+            # Bail out if minor grids are not in a uniform state.
+            raise ValueError
+        x_state, y_state = map(self._get_uniform_grid_state,
+                               [ax.xaxis.majorTicks, ax.yaxis.majorTicks])
+        cycle = self._cycle
+        # Bail out (via ValueError) if major grids are not in a uniform state.
+        x_state, y_state = (
+            cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
+        return (x_state, "major" if x_state else "both",
+                y_state, "major" if y_state else "both")
+
+
+class ToolMinorGrid(_ToolGridBase):
+    """Tool to toggle the major and minor grids of the figure"""
+
+    description = 'Toggle major and minor grids'
+    default_keymap = rcParams['keymap.grid_minor']
+
+    def _get_next_grid_states(self, ax):
+        if None in map(self._get_uniform_grid_state,
+                       [ax.xaxis.majorTicks, ax.yaxis.majorTicks]):
+            # Bail out if major grids are not in a uniform state.
+            raise ValueError
+        x_state, y_state = map(self._get_uniform_grid_state,
+                               [ax.xaxis.minorTicks, ax.yaxis.minorTicks])
+        cycle = self._cycle
+        # Bail out (via ValueError) if minor grids are not in a uniform state.
+        x_state, y_state = (
+            cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
+        return x_state, "both", y_state, "both"
+
+
+class ToolFullScreen(ToolToggleBase):
+    """Tool to toggle full screen"""
+
+    description = 'Toggle fullscreen mode'
+    default_keymap = rcParams['keymap.fullscreen']
+
+    def enable(self, event):
+        self.figure.canvas.manager.full_screen_toggle()
+
+    def disable(self, event):
+        self.figure.canvas.manager.full_screen_toggle()
+
+
+class AxisScaleBase(ToolToggleBase):
+    """Base Tool to toggle between linear and logarithmic"""
+
+    def trigger(self, sender, event, data=None):
+        if event.inaxes is None:
+            return
+        ToolToggleBase.trigger(self, sender, event, data)
+
+    def enable(self, event):
+        self.set_scale(event.inaxes, 'log')
+        self.figure.canvas.draw_idle()
+
+    def disable(self, event):
+        self.set_scale(event.inaxes, 'linear')
+        self.figure.canvas.draw_idle()
+
+
+class ToolYScale(AxisScaleBase):
+    """Tool to toggle between linear and logarithmic scales on the Y axis"""
+
+    description = 'Toggle scale Y axis'
+    default_keymap = rcParams['keymap.yscale']
+
+    def set_scale(self, ax, scale):
+        ax.set_yscale(scale)
+
+
+class ToolXScale(AxisScaleBase):
+    """Tool to toggle between linear and logarithmic scales on the X axis"""
+
+    description = 'Toggle scale X axis'
+    default_keymap = rcParams['keymap.xscale']
+
+    def set_scale(self, ax, scale):
+        ax.set_xscale(scale)
+
+
+class ToolViewsPositions(ToolBase):
+    """
+    Auxiliary Tool to handle changes in views and positions
+
+    Runs in the background and should get used by all the tools that
+    need to access the figure's history of views and positions, e.g.
+
+    * `ToolZoom`
+    * `ToolPan`
+    * `ToolHome`
+    * `ToolBack`
+    * `ToolForward`
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.views = WeakKeyDictionary()
+        self.positions = WeakKeyDictionary()
+        self.home_views = WeakKeyDictionary()
+        ToolBase.__init__(self, *args, **kwargs)
+
+    def add_figure(self, figure):
+        """Add the current figure to the stack of views and positions"""
+
+        if figure not in self.views:
+            self.views[figure] = cbook.Stack()
+            self.positions[figure] = cbook.Stack()
+            self.home_views[figure] = WeakKeyDictionary()
+            # Define Home
+            self.push_current(figure)
+            # Make sure we add a home view for new axes as they're added
+            figure.add_axobserver(lambda fig: self.update_home_views(fig))
+
+    def clear(self, figure):
+        """Reset the axes stack"""
+        if figure in self.views:
+            self.views[figure].clear()
+            self.positions[figure].clear()
+            self.home_views[figure].clear()
+            self.update_home_views()
+
+    def update_view(self):
+        """
+        Update the view limits and position for each axes from the current
+        stack position. If any axes are present in the figure that aren't in
+        the current stack position, use the home view limits for those axes and
+        don't update *any* positions.
+        """
+
+        views = self.views[self.figure]()
+        if views is None:
+            return
+        pos = self.positions[self.figure]()
+        if pos is None:
+            return
+        home_views = self.home_views[self.figure]
+        all_axes = self.figure.get_axes()
+        for a in all_axes:
+            if a in views:
+                cur_view = views[a]
+            else:
+                cur_view = home_views[a]
+            a._set_view(cur_view)
+
+        if set(all_axes).issubset(pos):
+            for a in all_axes:
+                # Restore both the original and modified positions
+                a._set_position(pos[a][0], 'original')
+                a._set_position(pos[a][1], 'active')
+
+        self.figure.canvas.draw_idle()
+
+    def push_current(self, figure=None):
+        """
+        Push the current view limits and position onto their respective stacks
+        """
+        if not figure:
+            figure = self.figure
+        views = WeakKeyDictionary()
+        pos = WeakKeyDictionary()
+        for a in figure.get_axes():
+            views[a] = a._get_view()
+            pos[a] = self._axes_pos(a)
+        self.views[figure].push(views)
+        self.positions[figure].push(pos)
+
+    def _axes_pos(self, ax):
+        """
+        Return the original and modified positions for the specified axes
+
+        Parameters
+        ----------
+        ax : (matplotlib.axes.AxesSubplot)
+        The axes to get the positions for
+
+        Returns
+        -------
+        limits : (tuple)
+        A tuple of the original and modified positions
+        """
+
+        return (ax.get_position(True).frozen(),
+                ax.get_position().frozen())
+
+    def update_home_views(self, figure=None):
+        """
+        Make sure that self.home_views has an entry for all axes present in the
+        figure
+        """
+
+        if not figure:
+            figure = self.figure
+        for a in figure.get_axes():
+            if a not in self.home_views[figure]:
+                self.home_views[figure][a] = a._get_view()
+
+    def refresh_locators(self):
+        """Redraw the canvases, update the locators"""
+        for a in self.figure.get_axes():
+            xaxis = getattr(a, 'xaxis', None)
+            yaxis = getattr(a, 'yaxis', None)
+            zaxis = getattr(a, 'zaxis', None)
+            locators = []
+            if xaxis is not None:
+                locators.append(xaxis.get_major_locator())
+                locators.append(xaxis.get_minor_locator())
+            if yaxis is not None:
+                locators.append(yaxis.get_major_locator())
+                locators.append(yaxis.get_minor_locator())
+            if zaxis is not None:
+                locators.append(zaxis.get_major_locator())
+                locators.append(zaxis.get_minor_locator())
+
+            for loc in locators:
+                loc.refresh()
+        self.figure.canvas.draw_idle()
+
+    def home(self):
+        """Recall the first view and position from the stack"""
+        self.views[self.figure].home()
+        self.positions[self.figure].home()
+
+    def back(self):
+        """Back one step in the stack of views and positions"""
+        self.views[self.figure].back()
+        self.positions[self.figure].back()
+
+    def forward(self):
+        """Forward one step in the stack of views and positions"""
+        self.views[self.figure].forward()
+        self.positions[self.figure].forward()
+
+
+class ViewsPositionsBase(ToolBase):
+    """Base class for `ToolHome`, `ToolBack` and `ToolForward`"""
+
+    _on_trigger = None
+
+    def trigger(self, sender, event, data=None):
+        self.toolmanager.get_tool(_views_positions).add_figure(self.figure)
+        getattr(self.toolmanager.get_tool(_views_positions),
+                self._on_trigger)()
+        self.toolmanager.get_tool(_views_positions).update_view()
+
+
+class ToolHome(ViewsPositionsBase):
+    """Restore the original view lim"""
+
+    description = 'Reset original view'
+    image = 'home'
+    default_keymap = rcParams['keymap.home']
+    _on_trigger = 'home'
+
+
+class ToolBack(ViewsPositionsBase):
+    """Move back up the view lim stack"""
+
+    description = 'Back to previous view'
+    image = 'back'
+    default_keymap = rcParams['keymap.back']
+    _on_trigger = 'back'
+
+
+class ToolForward(ViewsPositionsBase):
+    """Move forward in the view lim stack"""
+
+    description = 'Forward to next view'
+    image = 'forward'
+    default_keymap = rcParams['keymap.forward']
+    _on_trigger = 'forward'
+
+
+class ConfigureSubplotsBase(ToolBase):
+    """Base tool for the configuration of subplots"""
+
+    description = 'Configure subplots'
+    image = 'subplots'
+
+
+class SaveFigureBase(ToolBase):
+    """Base tool for figure saving"""
+
+    description = 'Save the figure'
+    image = 'filesave'
+    default_keymap = rcParams['keymap.save']
+
+
+class ZoomPanBase(ToolToggleBase):
+    """Base class for `ToolZoom` and `ToolPan`"""
+    def __init__(self, *args):
+        ToolToggleBase.__init__(self, *args)
+        self._button_pressed = None
+        self._xypress = None
+        self._idPress = None
+        self._idRelease = None
+        self._idScroll = None
+        self.base_scale = 2.
+        self.scrollthresh = .5  # .5 second scroll threshold
+        self.lastscroll = time.time()-self.scrollthresh
+
+    def enable(self, event):
+        """Connect press/release events and lock the canvas"""
+        self.figure.canvas.widgetlock(self)
+        self._idPress = self.figure.canvas.mpl_connect(
+            'button_press_event', self._press)
+        self._idRelease = self.figure.canvas.mpl_connect(
+            'button_release_event', self._release)
+        self._idScroll = self.figure.canvas.mpl_connect(
+            'scroll_event', self.scroll_zoom)
+
+    def disable(self, event):
+        """Release the canvas and disconnect press/release events"""
+        self._cancel_action()
+        self.figure.canvas.widgetlock.release(self)
+        self.figure.canvas.mpl_disconnect(self._idPress)
+        self.figure.canvas.mpl_disconnect(self._idRelease)
+        self.figure.canvas.mpl_disconnect(self._idScroll)
+
+    def trigger(self, sender, event, data=None):
+        self.toolmanager.get_tool(_views_positions).add_figure(self.figure)
+        ToolToggleBase.trigger(self, sender, event, data)
+
+    def scroll_zoom(self, event):
+        # https://gist.github.com/tacaswell/3144287
+        if event.inaxes is None:
+            return
+
+        if event.button == 'up':
+            # deal with zoom in
+            scl = self.base_scale
+        elif event.button == 'down':
+            # deal with zoom out
+            scl = 1/self.base_scale
+        else:
+            # deal with something that should never happen
+            scl = 1
+
+        ax = event.inaxes
+        ax._set_view_from_bbox([event.x, event.y, scl])
+
+        # If last scroll was done within the timing threshold, delete the
+        # previous view
+        if (time.time()-self.lastscroll) < self.scrollthresh:
+            self.toolmanager.get_tool(_views_positions).back()
+
+        self.figure.canvas.draw_idle()  # force re-draw
+
+        self.lastscroll = time.time()
+        self.toolmanager.get_tool(_views_positions).push_current()
+
+
+class ToolZoom(ZoomPanBase):
+    """Zoom to rectangle"""
+
+    description = 'Zoom to rectangle'
+    image = 'zoom_to_rect'
+    default_keymap = rcParams['keymap.zoom']
+    cursor = cursors.SELECT_REGION
+    radio_group = 'default'
+
+    def __init__(self, *args):
+        ZoomPanBase.__init__(self, *args)
+        self._ids_zoom = []
+
+    def _cancel_action(self):
+        for zoom_id in self._ids_zoom:
+            self.figure.canvas.mpl_disconnect(zoom_id)
+        self.toolmanager.trigger_tool('rubberband', self)
+        self.toolmanager.get_tool(_views_positions).refresh_locators()
+        self._xypress = None
+        self._button_pressed = None
+        self._ids_zoom = []
+        return
+
+    def _press(self, event):
+        """Callback for mouse button presses in zoom-to-rectangle mode."""
+
+        # If we're already in the middle of a zoom, pressing another
+        # button works to "cancel"
+        if self._ids_zoom != []:
+            self._cancel_action()
+
+        if event.button == 1:
+            self._button_pressed = 1
+        elif event.button == 3:
+            self._button_pressed = 3
+        else:
+            self._cancel_action()
+            return
+
+        x, y = event.x, event.y
+
+        self._xypress = []
+        for i, a in enumerate(self.figure.get_axes()):
+            if (x is not None and y is not None and a.in_axes(event) and
+                    a.get_navigate() and a.can_zoom()):
+                self._xypress.append((x, y, a, i, a._get_view()))
+
+        id1 = self.figure.canvas.mpl_connect(
+            'motion_notify_event', self._mouse_move)
+        id2 = self.figure.canvas.mpl_connect(
+            'key_press_event', self._switch_on_zoom_mode)
+        id3 = self.figure.canvas.mpl_connect(
+            'key_release_event', self._switch_off_zoom_mode)
+
+        self._ids_zoom = id1, id2, id3
+        self._zoom_mode = event.key
+
+    def _switch_on_zoom_mode(self, event):
+        self._zoom_mode = event.key
+        self._mouse_move(event)
+
+    def _switch_off_zoom_mode(self, event):
+        self._zoom_mode = None
+        self._mouse_move(event)
+
+    def _mouse_move(self, event):
+        """Callback for mouse moves in zoom-to-rectangle mode."""
+
+        if self._xypress:
+            x, y = event.x, event.y
+            lastx, lasty, a, ind, view = self._xypress[0]
+            (x1, y1), (x2, y2) = np.clip(
+                [[lastx, lasty], [x, y]], a.bbox.min, a.bbox.max)
+            if self._zoom_mode == "x":
+                y1, y2 = a.bbox.intervaly
+            elif self._zoom_mode == "y":
+                x1, x2 = a.bbox.intervalx
+            self.toolmanager.trigger_tool(
+                'rubberband', self, data=(x1, y1, x2, y2))
+
+    def _release(self, event):
+        """Callback for mouse button releases in zoom-to-rectangle mode."""
+
+        for zoom_id in self._ids_zoom:
+            self.figure.canvas.mpl_disconnect(zoom_id)
+        self._ids_zoom = []
+
+        if not self._xypress:
+            self._cancel_action()
+            return
+
+        last_a = []
+
+        for cur_xypress in self._xypress:
+            x, y = event.x, event.y
+            lastx, lasty, a, _ind, view = cur_xypress
+            # ignore singular clicks - 5 pixels is a threshold
+            if abs(x - lastx) < 5 or abs(y - lasty) < 5:
+                self._cancel_action()
+                return
+
+            # detect twinx, twiny axes and avoid double zooming
+            twinx, twiny = False, False
+            if last_a:
+                for la in last_a:
+                    if a.get_shared_x_axes().joined(a, la):
+                        twinx = True
+                    if a.get_shared_y_axes().joined(a, la):
+                        twiny = True
+            last_a.append(a)
+
+            if self._button_pressed == 1:
+                direction = 'in'
+            elif self._button_pressed == 3:
+                direction = 'out'
+            else:
+                continue
+
+            a._set_view_from_bbox((lastx, lasty, x, y), direction,
+                                  self._zoom_mode, twinx, twiny)
+
+        self._zoom_mode = None
+        self.toolmanager.get_tool(_views_positions).push_current()
+        self._cancel_action()
+
+
+class ToolPan(ZoomPanBase):
+    """Pan axes with left mouse, zoom with right"""
+
+    default_keymap = rcParams['keymap.pan']
+    description = 'Pan axes with left mouse, zoom with right'
+    image = 'move'
+    cursor = cursors.MOVE
+    radio_group = 'default'
+
+    def __init__(self, *args):
+        ZoomPanBase.__init__(self, *args)
+        self._idDrag = None
+
+    def _cancel_action(self):
+        self._button_pressed = None
+        self._xypress = []
+        self.figure.canvas.mpl_disconnect(self._idDrag)
+        self.toolmanager.messagelock.release(self)
+        self.toolmanager.get_tool(_views_positions).refresh_locators()
+
+    def _press(self, event):
+        if event.button == 1:
+            self._button_pressed = 1
+        elif event.button == 3:
+            self._button_pressed = 3
+        else:
+            self._cancel_action()
+            return
+
+        x, y = event.x, event.y
+
+        self._xypress = []
+        for i, a in enumerate(self.figure.get_axes()):
+            if (x is not None and y is not None and a.in_axes(event) and
+                    a.get_navigate() and a.can_pan()):
+                a.start_pan(x, y, event.button)
+                self._xypress.append((a, i))
+                self.toolmanager.messagelock(self)
+                self._idDrag = self.figure.canvas.mpl_connect(
+                    'motion_notify_event', self._mouse_move)
+
+    def _release(self, event):
+        if self._button_pressed is None:
+            self._cancel_action()
+            return
+
+        self.figure.canvas.mpl_disconnect(self._idDrag)
+        self.toolmanager.messagelock.release(self)
+
+        for a, _ind in self._xypress:
+            a.end_pan()
+        if not self._xypress:
+            self._cancel_action()
+            return
+
+        self.toolmanager.get_tool(_views_positions).push_current()
+        self._cancel_action()
+
+    def _mouse_move(self, event):
+        for a, _ind in self._xypress:
+            # safer to use the recorded button at the _press than current
+            # button: # multiple button can get pressed during motion...
+            a.drag_pan(self._button_pressed, event.key, event.x, event.y)
+        self.toolmanager.canvas.draw_idle()
+
+
+class ToolHelpBase(ToolBase):
+    description = 'Print tool list, shortcuts and description'
+    default_keymap = rcParams['keymap.help']
+    image = 'help.png'
+
+    @staticmethod
+    def format_shortcut(key_sequence):
+        """
+        Converts a shortcut string from the notation used in rc config to the
+        standard notation for displaying shortcuts, e.g. 'ctrl+a' -> 'Ctrl+A'.
+        """
+        return (key_sequence if len(key_sequence) == 1 else
+                re.sub(r"\+[A-Z]", r"+Shift\g<0>", key_sequence).title())
+
+    def _format_tool_keymap(self, name):
+        keymaps = self.toolmanager.get_tool_keymap(name)
+        return ", ".join(self.format_shortcut(keymap) for keymap in keymaps)
+
+    def _get_help_entries(self):
+        return [(name, self._format_tool_keymap(name), tool.description)
+                for name, tool in sorted(self.toolmanager.tools.items())
+                if tool.description]
+
+    def _get_help_text(self):
+        entries = self._get_help_entries()
+        entries = ["{}: {}\n\t{}".format(*entry) for entry in entries]
+        return "\n".join(entries)
+
+    def _get_help_html(self):
+        fmt = "<tr><td>{}</td><td>{}</td><td>{}</td></tr>"
+        rows = [fmt.format(
+            "<b>Action</b>", "<b>Shortcuts</b>", "<b>Description</b>")]
+        rows += [fmt.format(*row) for row in self._get_help_entries()]
+        return ("<style>td {padding: 0px 4px}</style>"
+                "<table><thead>" + rows[0] + "</thead>"
+                "<tbody>".join(rows[1:]) + "</tbody></table>")
+
+
+class ToolCopyToClipboardBase(ToolBase):
+    """Tool to copy the figure to the clipboard"""
+
+    description = 'Copy the canvas figure to clipboard'
+    default_keymap = rcParams['keymap.copy']
+
+    def trigger(self, *args, **kwargs):
+        message = "Copy tool is not available"
+        self.toolmanager.message_event(message, self)
+
+
+default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward,
+                 'zoom': ToolZoom, 'pan': ToolPan,
+                 'subplots': 'ToolConfigureSubplots',
+                 'save': 'ToolSaveFigure',
+                 'grid': ToolGrid,
+                 'grid_minor': ToolMinorGrid,
+                 'fullscreen': ToolFullScreen,
+                 'quit': ToolQuit,
+                 'quit_all': ToolQuitAll,
+                 'allnav': ToolEnableAllNavigation,
+                 'nav': ToolEnableNavigation,
+                 'xscale': ToolXScale,
+                 'yscale': ToolYScale,
+                 'position': ToolCursorPosition,
+                 _views_positions: ToolViewsPositions,
+                 'cursor': 'ToolSetCursor',
+                 'rubberband': 'ToolRubberband',
+                 'help': 'ToolHelp',
+                 'copy': 'ToolCopyToClipboard',
+                 }
+"""Default tools"""
+
+default_toolbar_tools = [['navigation', ['home', 'back', 'forward']],
+                         ['zoompan', ['pan', 'zoom', 'subplots']],
+                         ['io', ['save', 'help']]]
+"""Default tools in the toolbar"""
+
+
+def add_tools_to_manager(toolmanager, tools=default_tools):
+    """
+    Add multiple tools to `ToolManager`
+
+    Parameters
+    ----------
+    toolmanager : ToolManager
+        `backend_managers.ToolManager` object that will get the tools added
+    tools : {str: class_like}, optional
+        The tools to add in a {name: tool} dict, see `add_tool` for more
+        info.
+    """
+
+    for name, tool in tools.items():
+        toolmanager.add_tool(name, tool)
+
+
+def add_tools_to_container(container, tools=default_toolbar_tools):
+    """
+    Add multiple tools to the container.
+
+    Parameters
+    ----------
+    container : Container
+        `backend_bases.ToolContainerBase` object that will get the tools added
+    tools : list, optional
+        List in the form
+        [[group1, [tool1, tool2 ...]], [group2, [...]]]
+        Where the tools given by tool1, and tool2 will display in group1.
+        See `add_tool` for details.
+    """
+
+    for group, grouptools in tools:
+        for position, tool in enumerate(grouptools):
+            container.add_tool(tool, group, position)

+ 54 - 0
venv/lib/python3.8/site-packages/matplotlib/backends/__init__.py

@@ -0,0 +1,54 @@
+import importlib
+import logging
+import os
+import sys
+
+import matplotlib
+from matplotlib import cbook
+from matplotlib.backend_bases import _Backend
+
+_log = logging.getLogger(__name__)
+
+
+# NOTE: plt.switch_backend() (called at import time) will add a "backend"
+# attribute here for backcompat.
+
+
+def _get_running_interactive_framework():
+    """
+    Return the interactive framework whose event loop is currently running, if
+    any, or "headless" if no event loop can be started, or None.
+
+    Returns
+    -------
+    Optional[str]
+        One of the following values: "qt5", "qt4", "gtk3", "wx", "tk",
+        "macosx", "headless", ``None``.
+    """
+    QtWidgets = (sys.modules.get("PyQt5.QtWidgets")
+                 or sys.modules.get("PySide2.QtWidgets"))
+    if QtWidgets and QtWidgets.QApplication.instance():
+        return "qt5"
+    QtGui = (sys.modules.get("PyQt4.QtGui")
+             or sys.modules.get("PySide.QtGui"))
+    if QtGui and QtGui.QApplication.instance():
+        return "qt4"
+    Gtk = sys.modules.get("gi.repository.Gtk")
+    if Gtk and Gtk.main_level():
+        return "gtk3"
+    wx = sys.modules.get("wx")
+    if wx and wx.GetApp():
+        return "wx"
+    tkinter = sys.modules.get("tkinter")
+    if tkinter:
+        for frame in sys._current_frames().values():
+            while frame:
+                if frame.f_code == tkinter.mainloop.__code__:
+                    return "tk"
+                frame = frame.f_back
+    if 'matplotlib.backends._macosx' in sys.modules:
+        if sys.modules["matplotlib.backends._macosx"].event_loop_is_running():
+            return "macosx"
+    if sys.platform.startswith("linux") and not os.environ.get("DISPLAY"):
+        return "headless"
+    return None

BIN
venv/lib/python3.8/site-packages/matplotlib/backends/_backend_agg.cpython-38-x86_64-linux-gnu.so


Неке датотеке нису приказане због велике количине промена