pruned venvs

This commit is contained in:
d3m1g0d
2019-03-12 21:57:16 +01:00
parent 33f0511081
commit e441f4f7f7
5988 changed files with 0 additions and 1353666 deletions
Binary file not shown.
-76
View File
@@ -1,76 +0,0 @@
# 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="/root/projekti/TeraHz/utils/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
View File
@@ -1,37 +0,0 @@
# 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 "/root/projekti/TeraHz/utils/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
View File
@@ -1,75 +0,0 @@
# 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 "/root/projekti/TeraHz/utils/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
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- 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())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- 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())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- 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())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- 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())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- 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())
-976
View File
@@ -1,976 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
#
# Very simple serial terminal
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier: BSD-3-Clause
import codecs
import os
import sys
import threading
import serial
from serial.tools.list_ports import comports
from serial.tools import hexlify_codec
# pylint: disable=wrong-import-order,wrong-import-position
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
try:
raw_input
except NameError:
# pylint: disable=redefined-builtin,invalid-name
raw_input = input # in python3 it's "raw"
unichr = chr
def key_description(character):
"""generate a readable description for a key"""
ascii_code = ord(character)
if ascii_code < 32:
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
else:
return repr(character)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class ConsoleBase(object):
"""OS abstraction for console (input/output codec, no echo)"""
def __init__(self):
if sys.version_info >= (3, 0):
self.byte_output = sys.stdout.buffer
else:
self.byte_output = sys.stdout
self.output = sys.stdout
def setup(self):
"""Set console to read single characters, no echo"""
def cleanup(self):
"""Restore default console settings"""
def getkey(self):
"""Read a single key from the console"""
return None
def write_bytes(self, byte_string):
"""Write bytes (already encoded)"""
self.byte_output.write(byte_string)
self.byte_output.flush()
def write(self, text):
"""Write string"""
self.output.write(text)
self.output.flush()
def cancel(self):
"""Cancel getkey operation"""
# - - - - - - - - - - - - - - - - - - - - - - - -
# context manager:
# switch terminal temporary to normal mode (e.g. to get user input)
def __enter__(self):
self.cleanup()
return self
def __exit__(self, *args, **kwargs):
self.setup()
if os.name == 'nt': # noqa
import msvcrt
import ctypes
class Out(object):
"""file-like wrapper that uses os.write"""
def __init__(self, fd):
self.fd = fd
def flush(self):
pass
def write(self, s):
os.write(self.fd, s)
class Console(ConsoleBase):
def __init__(self):
super(Console, self).__init__()
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
ctypes.windll.kernel32.SetConsoleCP(65001)
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
# the change of the code page is not propagated to Python, manually fix it
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
sys.stdout = self.output
self.output.encoding = 'UTF-8' # needed for input
def __del__(self):
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
def getkey(self):
while True:
z = msvcrt.getwch()
if z == unichr(13):
return unichr(10)
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
msvcrt.getwch()
else:
return z
def cancel(self):
# CancelIo, CancelSynchronousIo do not seem to work when using
# getwch, so instead, send a key to the window with the console
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
elif os.name == 'posix':
import atexit
import termios
import fcntl
class Console(ConsoleBase):
def __init__(self):
super(Console, self).__init__()
self.fd = sys.stdin.fileno()
self.old = termios.tcgetattr(self.fd)
atexit.register(self.cleanup)
if sys.version_info < (3, 0):
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
else:
self.enc_stdin = sys.stdin
def setup(self):
new = termios.tcgetattr(self.fd)
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
new[6][termios.VMIN] = 1
new[6][termios.VTIME] = 0
termios.tcsetattr(self.fd, termios.TCSANOW, new)
def getkey(self):
c = self.enc_stdin.read(1)
if c == unichr(0x7f):
c = unichr(8) # map the BS key (which yields DEL) to backspace
return c
def cancel(self):
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
def cleanup(self):
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
else:
raise NotImplementedError(
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class Transform(object):
"""do-nothing: forward all data unchanged"""
def rx(self, text):
"""text received from serial port"""
return text
def tx(self, text):
"""text to be sent to serial port"""
return text
def echo(self, text):
"""text to be sent but displayed on console"""
return text
class CRLF(Transform):
"""ENTER sends CR+LF"""
def tx(self, text):
return text.replace('\n', '\r\n')
class CR(Transform):
"""ENTER sends CR"""
def rx(self, text):
return text.replace('\r', '\n')
def tx(self, text):
return text.replace('\n', '\r')
class LF(Transform):
"""ENTER sends LF"""
class NoTerminal(Transform):
"""remove typical terminal control codes from input"""
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
REPLACEMENT_MAP.update(
{
0x7F: 0x2421, # DEL
0x9B: 0x2425, # CSI
})
def rx(self, text):
return text.translate(self.REPLACEMENT_MAP)
echo = rx
class NoControls(NoTerminal):
"""Remove all control codes, incl. CR+LF"""
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
REPLACEMENT_MAP.update(
{
0x20: 0x2423, # visual space
0x7F: 0x2421, # DEL
0x9B: 0x2425, # CSI
})
class Printable(Transform):
"""Show decimal code for all non-ASCII characters and replace most control codes"""
def rx(self, text):
r = []
for c in text:
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
r.append(c)
elif c < ' ':
r.append(unichr(0x2400 + ord(c)))
else:
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
r.append(' ')
return ''.join(r)
echo = rx
class Colorize(Transform):
"""Apply different colors for received and echo"""
def __init__(self):
# XXX make it configurable, use colorama?
self.input_color = '\x1b[37m'
self.echo_color = '\x1b[31m'
def rx(self, text):
return self.input_color + text
def echo(self, text):
return self.echo_color + text
class DebugIO(Transform):
"""Print what is sent and received"""
def rx(self, text):
sys.stderr.write(' [RX:{}] '.format(repr(text)))
sys.stderr.flush()
return text
def tx(self, text):
sys.stderr.write(' [TX:{}] '.format(repr(text)))
sys.stderr.flush()
return text
# other ideas:
# - add date/time for each newline
# - insert newline after: a) timeout b) packet end character
EOL_TRANSFORMATIONS = {
'crlf': CRLF,
'cr': CR,
'lf': LF,
}
TRANSFORMATIONS = {
'direct': Transform, # no transformation
'default': NoTerminal,
'nocontrol': NoControls,
'printable': Printable,
'colorize': Colorize,
'debug': DebugIO,
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def ask_for_port():
"""\
Show a list of ports and ask the user for a choice. To make selection
easier on systems with long device names, also allow the input of an
index.
"""
sys.stderr.write('\n--- Available ports:\n')
ports = []
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
ports.append(port)
while True:
port = raw_input('--- Enter port index or full name: ')
try:
index = int(port) - 1
if not 0 <= index < len(ports):
sys.stderr.write('--- Invalid index!\n')
continue
except ValueError:
pass
else:
port = ports[index]
return port
class Miniterm(object):
"""\
Terminal application. Copy data from serial port to console and vice versa.
Handle special keys from the console to show menu etc.
"""
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
self.console = Console()
self.serial = serial_instance
self.echo = echo
self.raw = False
self.input_encoding = 'UTF-8'
self.output_encoding = 'UTF-8'
self.eol = eol
self.filters = filters
self.update_transformations()
self.exit_character = 0x1d # GS/CTRL+]
self.menu_character = 0x14 # Menu: CTRL+T
self.alive = None
self._reader_alive = None
self.receiver_thread = None
self.rx_decoder = None
self.tx_decoder = None
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
# start serial->console thread
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
self.receiver_thread.daemon = True
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
if hasattr(self.serial, 'cancel_read'):
self.serial.cancel_read()
self.receiver_thread.join()
def start(self):
"""start worker threads"""
self.alive = True
self._start_reader()
# enter console->serial loop
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
self.transmitter_thread.daemon = True
self.transmitter_thread.start()
self.console.setup()
def stop(self):
"""set flag to stop worker threads"""
self.alive = False
def join(self, transmit_only=False):
"""wait for worker threads to terminate"""
self.transmitter_thread.join()
if not transmit_only:
if hasattr(self.serial, 'cancel_read'):
self.serial.cancel_read()
self.receiver_thread.join()
def close(self):
self.serial.close()
def update_transformations(self):
"""take list of transformation classes and instantiate them for rx and tx"""
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
for f in self.filters]
self.tx_transformations = [t() for t in transformations]
self.rx_transformations = list(reversed(self.tx_transformations))
def set_rx_encoding(self, encoding, errors='replace'):
"""set encoding for received data"""
self.input_encoding = encoding
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
def set_tx_encoding(self, encoding, errors='replace'):
"""set encoding for transmitted data"""
self.output_encoding = encoding
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
def dump_port_settings(self):
"""Write current settings to sys.stderr"""
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
p=self.serial))
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
('active' if self.serial.rts else 'inactive'),
('active' if self.serial.dtr else 'inactive'),
('active' if self.serial.break_condition else 'inactive')))
try:
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
('active' if self.serial.cts else 'inactive'),
('active' if self.serial.dsr else 'inactive'),
('active' if self.serial.ri else 'inactive'),
('active' if self.serial.cd else 'inactive')))
except serial.SerialException:
# on RFC 2217 ports, it can happen if no modem state notification was
# yet received. ignore this error.
pass
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
def reader(self):
"""loop and copy serial->console"""
try:
while self.alive and self._reader_alive:
# read all that is there or wait for one byte
data = self.serial.read(self.serial.in_waiting or 1)
if data:
if self.raw:
self.console.write_bytes(data)
else:
text = self.rx_decoder.decode(data)
for transformation in self.rx_transformations:
text = transformation.rx(text)
self.console.write(text)
except serial.SerialException:
self.alive = False
self.console.cancel()
raise # XXX handle instead of re-raise?
def writer(self):
"""\
Loop and copy console->serial until self.exit_character character is
found. When self.menu_character is found, interpret the next key
locally.
"""
menu_active = False
try:
while self.alive:
try:
c = self.console.getkey()
except KeyboardInterrupt:
c = '\x03'
if not self.alive:
break
if menu_active:
self.handle_menu_key(c)
menu_active = False
elif c == self.menu_character:
menu_active = True # next char will be for menu
elif c == self.exit_character:
self.stop() # exit app
break
else:
#~ if self.raw:
text = c
for transformation in self.tx_transformations:
text = transformation.tx(text)
self.serial.write(self.tx_encoder.encode(text))
if self.echo:
echo_text = c
for transformation in self.tx_transformations:
echo_text = transformation.echo(echo_text)
self.console.write(echo_text)
except:
self.alive = False
raise
def handle_menu_key(self, c):
"""Implement a simple menu / settings"""
if c == self.menu_character or c == self.exit_character:
# Menu/exit character again -> send itself
self.serial.write(self.tx_encoder.encode(c))
if self.echo:
self.console.write(c)
elif c == '\x15': # CTRL+U -> upload file
self.upload_file()
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
sys.stderr.write(self.get_help_text())
elif c == '\x12': # CTRL+R -> Toggle RTS
self.serial.rts = not self.serial.rts
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
elif c == '\x04': # CTRL+D -> Toggle DTR
self.serial.dtr = not self.serial.dtr
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
elif c == '\x02': # CTRL+B -> toggle BREAK condition
self.serial.break_condition = not self.serial.break_condition
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
elif c == '\x05': # CTRL+E -> toggle local echo
self.echo = not self.echo
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
elif c == '\x06': # CTRL+F -> edit filters
self.change_filter()
elif c == '\x0c': # CTRL+L -> EOL mode
modes = list(EOL_TRANSFORMATIONS) # keys
eol = modes.index(self.eol) + 1
if eol >= len(modes):
eol = 0
self.eol = modes[eol]
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
self.update_transformations()
elif c == '\x01': # CTRL+A -> set encoding
self.change_encoding()
elif c == '\x09': # CTRL+I -> info
self.dump_port_settings()
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
elif c in 'pP': # P -> change port
self.change_port()
elif c in 'sS': # S -> suspend / open port temporarily
self.suspend_port()
elif c in 'bB': # B -> change baudrate
self.change_baudrate()
elif c == '8': # 8 -> change to 8 bits
self.serial.bytesize = serial.EIGHTBITS
self.dump_port_settings()
elif c == '7': # 7 -> change to 8 bits
self.serial.bytesize = serial.SEVENBITS
self.dump_port_settings()
elif c in 'eE': # E -> change to even parity
self.serial.parity = serial.PARITY_EVEN
self.dump_port_settings()
elif c in 'oO': # O -> change to odd parity
self.serial.parity = serial.PARITY_ODD
self.dump_port_settings()
elif c in 'mM': # M -> change to mark parity
self.serial.parity = serial.PARITY_MARK
self.dump_port_settings()
elif c in 'sS': # S -> change to space parity
self.serial.parity = serial.PARITY_SPACE
self.dump_port_settings()
elif c in 'nN': # N -> change to no parity
self.serial.parity = serial.PARITY_NONE
self.dump_port_settings()
elif c == '1': # 1 -> change to 1 stop bits
self.serial.stopbits = serial.STOPBITS_ONE
self.dump_port_settings()
elif c == '2': # 2 -> change to 2 stop bits
self.serial.stopbits = serial.STOPBITS_TWO
self.dump_port_settings()
elif c == '3': # 3 -> change to 1.5 stop bits
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
self.dump_port_settings()
elif c in 'xX': # X -> change software flow control
self.serial.xonxoff = (c == 'X')
self.dump_port_settings()
elif c in 'rR': # R -> change hardware flow control
self.serial.rtscts = (c == 'R')
self.dump_port_settings()
else:
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
def upload_file(self):
"""Ask user for filenname and send its contents"""
sys.stderr.write('\n--- File to upload: ')
sys.stderr.flush()
with self.console:
filename = sys.stdin.readline().rstrip('\r\n')
if filename:
try:
with open(filename, 'rb') as f:
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
while True:
block = f.read(1024)
if not block:
break
self.serial.write(block)
# Wait for output buffer to drain.
self.serial.flush()
sys.stderr.write('.') # Progress indicator.
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
except IOError as e:
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
def change_filter(self):
"""change the i/o transformations"""
sys.stderr.write('\n--- Available Filters:\n')
sys.stderr.write('\n'.join(
'--- {:<10} = {.__doc__}'.format(k, v)
for k, v in sorted(TRANSFORMATIONS.items())))
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
with self.console:
new_filters = sys.stdin.readline().lower().split()
if new_filters:
for f in new_filters:
if f not in TRANSFORMATIONS:
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
break
else:
self.filters = new_filters
self.update_transformations()
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
def change_encoding(self):
"""change encoding on the serial port"""
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
with self.console:
new_encoding = sys.stdin.readline().strip()
if new_encoding:
try:
codecs.lookup(new_encoding)
except LookupError:
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
else:
self.set_rx_encoding(new_encoding)
self.set_tx_encoding(new_encoding)
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
def change_baudrate(self):
"""change the baudrate"""
sys.stderr.write('\n--- Baudrate: ')
sys.stderr.flush()
with self.console:
backup = self.serial.baudrate
try:
self.serial.baudrate = int(sys.stdin.readline().strip())
except ValueError as e:
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
self.serial.baudrate = backup
else:
self.dump_port_settings()
def change_port(self):
"""Have a conversation with the user to change the serial port"""
with self.console:
try:
port = ask_for_port()
except KeyboardInterrupt:
port = None
if port and port != self.serial.port:
# reader thread needs to be shut down
self._stop_reader()
# save settings
settings = self.serial.getSettingsDict()
try:
new_serial = serial.serial_for_url(port, do_not_open=True)
# restore settings and open
new_serial.applySettingsDict(settings)
new_serial.rts = self.serial.rts
new_serial.dtr = self.serial.dtr
new_serial.open()
new_serial.break_condition = self.serial.break_condition
except Exception as e:
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
new_serial.close()
else:
self.serial.close()
self.serial = new_serial
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
# and restart the reader thread
self._start_reader()
def suspend_port(self):
"""\
open port temporarily, allow reconnect, exit and port change to get
out of the loop
"""
# reader thread needs to be shut down
self._stop_reader()
self.serial.close()
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
do_change_port = False
while not self.serial.is_open:
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
exit=key_description(self.exit_character)))
k = self.console.getkey()
if k == self.exit_character:
self.stop() # exit app
break
elif k in 'pP':
do_change_port = True
break
try:
self.serial.open()
except Exception as e:
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
if do_change_port:
self.change_port()
else:
# and restart the reader thread
self._start_reader()
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
def get_help_text(self):
"""return the help text"""
# help text, starts with blank line!
return """
--- pySerial ({version}) - miniterm - help
---
--- {exit:8} Exit program
--- {menu:8} Menu escape key, followed by:
--- Menu keys:
--- {menu:7} Send the menu character itself to remote
--- {exit:7} Send the exit character itself to remote
--- {info:7} Show info
--- {upload:7} Upload file (prompt will be shown)
--- {repr:7} encoding
--- {filter:7} edit filters
--- Toggles:
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
--- {echo:7} echo {eol:7} EOL
---
--- Port settings ({menu} followed by the following):
--- p change port
--- 7 8 set data bits
--- N E O S M change parity (None, Even, Odd, Space, Mark)
--- 1 2 3 set stop bits (1, 2, 1.5)
--- b change baud rate
--- x X disable/enable software flow control
--- r R disable/enable hardware flow control
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
exit=key_description(self.exit_character),
menu=key_description(self.menu_character),
rts=key_description('\x12'),
dtr=key_description('\x04'),
brk=key_description('\x02'),
echo=key_description('\x05'),
info=key_description('\x09'),
upload=key_description('\x15'),
repr=key_description('\x01'),
filter=key_description('\x06'),
eol=key_description('\x0c'))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# default args can be used to override when calling main() from an other script
# e.g to create a miniterm-my-device.py
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
"""Command line tool, entry point"""
import argparse
parser = argparse.ArgumentParser(
description="Miniterm - A simple terminal program for the serial port.")
parser.add_argument(
"port",
nargs='?',
help="serial port name ('-' to show port list)",
default=default_port)
parser.add_argument(
"baudrate",
nargs='?',
type=int,
help="set baud rate, default: %(default)s",
default=default_baudrate)
group = parser.add_argument_group("port settings")
group.add_argument(
"--parity",
choices=['N', 'E', 'O', 'S', 'M'],
type=lambda c: c.upper(),
help="set parity, one of {N E O S M}, default: N",
default='N')
group.add_argument(
"--rtscts",
action="store_true",
help="enable RTS/CTS flow control (default off)",
default=False)
group.add_argument(
"--xonxoff",
action="store_true",
help="enable software flow control (default off)",
default=False)
group.add_argument(
"--rts",
type=int,
help="set initial RTS line state (possible values: 0, 1)",
default=default_rts)
group.add_argument(
"--dtr",
type=int,
help="set initial DTR line state (possible values: 0, 1)",
default=default_dtr)
group.add_argument(
"--ask",
action="store_true",
help="ask again for port when open fails",
default=False)
group = parser.add_argument_group("data handling")
group.add_argument(
"-e", "--echo",
action="store_true",
help="enable local echo (default off)",
default=False)
group.add_argument(
"--encoding",
dest="serial_port_encoding",
metavar="CODEC",
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
default='UTF-8')
group.add_argument(
"-f", "--filter",
action="append",
metavar="NAME",
help="add text transformation",
default=[])
group.add_argument(
"--eol",
choices=['CR', 'LF', 'CRLF'],
type=lambda c: c.upper(),
help="end of line mode",
default='CRLF')
group.add_argument(
"--raw",
action="store_true",
help="Do no apply any encodings/transformations",
default=False)
group = parser.add_argument_group("hotkeys")
group.add_argument(
"--exit-char",
type=int,
metavar='NUM',
help="Unicode of special character that is used to exit the application, default: %(default)s",
default=0x1d) # GS/CTRL+]
group.add_argument(
"--menu-char",
type=int,
metavar='NUM',
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
default=0x14) # Menu: CTRL+T
group = parser.add_argument_group("diagnostics")
group.add_argument(
"-q", "--quiet",
action="store_true",
help="suppress non-error messages",
default=False)
group.add_argument(
"--develop",
action="store_true",
help="show Python traceback on error",
default=False)
args = parser.parse_args()
if args.menu_char == args.exit_char:
parser.error('--exit-char can not be the same as --menu-char')
if args.filter:
if 'help' in args.filter:
sys.stderr.write('Available filters:\n')
sys.stderr.write('\n'.join(
'{:<10} = {.__doc__}'.format(k, v)
for k, v in sorted(TRANSFORMATIONS.items())))
sys.stderr.write('\n')
sys.exit(1)
filters = args.filter
else:
filters = ['default']
while True:
# no port given on command line -> ask user now
if args.port is None or args.port == '-':
try:
args.port = ask_for_port()
except KeyboardInterrupt:
sys.stderr.write('\n')
parser.error('user aborted and port is not given')
else:
if not args.port:
parser.error('port is not given')
try:
serial_instance = serial.serial_for_url(
args.port,
args.baudrate,
parity=args.parity,
rtscts=args.rtscts,
xonxoff=args.xonxoff,
do_not_open=True)
if not hasattr(serial_instance, 'cancel_read'):
# enable timeout for alive flag polling if cancel_read is not available
serial_instance.timeout = 1
if args.dtr is not None:
if not args.quiet:
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
serial_instance.dtr = args.dtr
if args.rts is not None:
if not args.quiet:
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
serial_instance.rts = args.rts
serial_instance.open()
except serial.SerialException as e:
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
if args.develop:
raise
if not args.ask:
sys.exit(1)
else:
args.port = '-'
else:
break
miniterm = Miniterm(
serial_instance,
echo=args.echo,
eol=args.eol.lower(),
filters=filters)
miniterm.exit_character = unichr(args.exit_char)
miniterm.menu_character = unichr(args.menu_char)
miniterm.raw = args.raw
miniterm.set_rx_encoding(args.serial_port_encoding)
miniterm.set_tx_encoding(args.serial_port_encoding)
if not args.quiet:
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
p=miniterm.serial))
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
key_description(miniterm.exit_character),
key_description(miniterm.menu_character),
key_description(miniterm.menu_character),
key_description('\x08')))
miniterm.start()
try:
miniterm.join(True)
except KeyboardInterrupt:
pass
if not args.quiet:
sys.stderr.write("\n--- exit ---\n")
miniterm.join()
miniterm.close()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if __name__ == '__main__':
main()
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-1
View File
@@ -1 +0,0 @@
python3
-1
View File
@@ -1 +0,0 @@
/usr/bin/python3
@@ -1,25 +0,0 @@
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
@@ -1,9 +0,0 @@
__pycache__/cycler.cpython-36.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
@@ -1,6 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
@@ -1 +0,0 @@
{"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,558 +0,0 @@
"""
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)
@@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
try:
from ._version import version as __version__
except ImportError:
__version__ = 'unknown'
__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
'utils', 'zoneinfo']
@@ -1,43 +0,0 @@
"""
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
@@ -1,4 +0,0 @@
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
version = '2.7.5'
@@ -1,89 +0,0 @@
# -*- 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))
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
from ._parser import parse, parser, parserinfo
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
from ._parser import UnknownTimezoneWarning
from ._parser import __doc__
from .isoparser import isoparser, isoparse
__all__ = ['parse', 'parser', 'parserinfo',
'isoparse', 'isoparser',
'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)
File diff suppressed because it is too large Load Diff
@@ -1,406 +0,0 @@
# -*- 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.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
Midnight is a special case for `hh`, as the standard supports both
00:00 and 24:00 as a representation.
.. 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')
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
"""
return time(*self._parse_isotime(timestr))
@_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
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
_DATE_SEP = b'-'
_TIME_SEP = b':'
_MICRO_SEP = b'.'
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'-+Z':
# 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:
# Microsecond
if timestr[pos:pos + 1] != self._MICRO_SEP:
continue
pos += 1
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
1)[0]
components[comp] = int(us_str) * 10**(6 - len(us_str))
pos += len(us_str)
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')
components[0] = 0
return components
def _parse_tzstr(self, tzstr, zero_as_utc=True):
if tzstr == b'Z':
return tz.tzutc()
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.tzutc()
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
@@ -1,590 +0,0 @@
# -*- 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 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 aritmetic operation on the original datetime value
with the information in the relativedelta.
weekday:
One of the weekday instances (MO, TU, etc). 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. Notice that
if the calculated date is already Monday, for example,
using MO(1) or MO(-1) won't change the day.
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
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
datetime(2018, 4, 2, 14, 37, 0)
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
File diff suppressed because it is too large Load Diff
@@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
from .tz import *
from .tz import __doc__
#: Convenience constant providing a :class:`tzutc()` instance
#:
#: .. versionadded:: 2.7.0
UTC = tzutc()
__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."""
@@ -1,415 +0,0 @@
from six import PY3
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
"""
def adjust_encoding(*args, **kwargs):
name = namefunc(*args, **kwargs)
if name is not None and not PY3:
name = name.encode()
return name
return adjust_encoding
# 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
occurence, 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
occurance, 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__
@@ -1,49 +0,0 @@
from datetime import timedelta
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 = {}
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))
return instance
class _TzStrFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
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))
return instance
File diff suppressed because it is too large Load Diff
@@ -1,331 +0,0 @@
# 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 if 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 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):
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):
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
@@ -1,2 +0,0 @@
# tzwin has moved to dateutil.tz.win
from .tz.win import *
@@ -1,71 +0,0 @@
# -*- 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 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
@@ -1,167 +0,0 @@
# -*- 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
@@ -1,53 +0,0 @@
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,5 +0,0 @@
"""Run the EasyInstall command"""
if __name__ == '__main__':
from setuptools.command.easy_install import main
main()
@@ -1,16 +0,0 @@
Welcome to Kiwi
===============
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
:target: https://travis-ci.org/nucleic/kiwi
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.
@@ -1,28 +0,0 @@
Metadata-Version: 2.0
Name: kiwisolver
Version: 1.0.1
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: UNKNOWN
Description-Content-Type: UNKNOWN
Platform: UNKNOWN
Requires-Dist: setuptools
Welcome to Kiwi
===============
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
:target: https://travis-ci.org/nucleic/kiwi
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.
@@ -1,8 +0,0 @@
kiwisolver-1.0.1.dist-info/DESCRIPTION.rst,sha256=Qy5sjKaN4toH_Q7EUHWgwRVmpxBgeGUwEcyB75zRwSI,676
kiwisolver-1.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
kiwisolver-1.0.1.dist-info/METADATA,sha256=bFtjRI423l1EopYow7-FVdw80WP942cTW4I37mnnunU,1006
kiwisolver-1.0.1.dist-info/RECORD,,
kiwisolver-1.0.1.dist-info/WHEEL,sha256=xLbWRW0PO79-mIB5Y7BBxUY9L97LDdQCs-obTuQSLEg,109
kiwisolver-1.0.1.dist-info/metadata.json,sha256=H-tQ6em1_t3jGeivswhlBiHFJ7CBAI6G_RZAsT-QHCs,535
kiwisolver-1.0.1.dist-info/top_level.txt,sha256=xqwWj7oSHlpIjcw2QMJb8puTFPdjDBO78AZp9gjTh9c,11
kiwisolver.cpython-36m-x86_64-linux-gnu.so,sha256=4GkBV_S_pJ93VLqTL5uPrsTtcIaTx6RIvdblto_AmVg,3860760
@@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: false
Tag: cp36-cp36m-manylinux1_x86_64
@@ -1 +0,0 @@
{"description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "sccolbert@gmail.com", "name": "The Nucleic Development Team", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/nucleic/kiwi"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "metadata_version": "2.0", "name": "kiwisolver", "run_requires": [{"requires": ["setuptools"]}], "summary": "A fast implementation of the Cassowary constraint solver", "version": "1.0.1"}
@@ -1,2 +0,0 @@
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)
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,33 +0,0 @@
Metadata-Version: 2.1
Name: matplotlib
Version: 3.0.2
Summary: Python plotting package
Home-page: http://matplotlib.org
Author: John D. Hunter, Michael Droettboom
Author-email: matplotlib-users@python.org
License: BSD
Download-URL: http://matplotlib.org/users/installing.html
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Python Software Foundation License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.5
Requires-Dist: numpy (>=1.10.0)
Requires-Dist: cycler (>=0.10)
Requires-Dist: kiwisolver (>=1.0.1)
Requires-Dist: pyparsing (!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1)
Requires-Dist: python-dateutil (>=2.1)
Matplotlib strives to produce publication quality 2D graphics
for interactive graphing, scientific publishing, user interface
development and web application servers targeting multiple user
interfaces and hardcopy output formats.
@@ -1,919 +0,0 @@
__pycache__/pylab.cpython-36.pyc,,
matplotlib-3.0.2-py3.6-nspkg.pth,sha256=HBCg6BgtP04BPqHtaNyC13Cbp9tWa8jd68ltGEV1XF8,1138
matplotlib-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
matplotlib-3.0.2.dist-info/METADATA,sha256=fs4NGrFCD56DFwQffsSExcU818TFY8T9OTinV1dNP5w,1226
matplotlib-3.0.2.dist-info/RECORD,,
matplotlib-3.0.2.dist-info/WHEEL,sha256=d2ILPScH-y2UwGxsW1PeA2TT-KW0Git4AJ6LeOK8sQo,109
matplotlib-3.0.2.dist-info/namespace_packages.txt,sha256=LQMWCv385LtvVcrCFmS8Kk1axcWAaUG9ycvuMhV6yoA,26
matplotlib-3.0.2.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=1IGoOjRpujOMRn7cZ29ERtAxBt6SxTUlRLBkSqa_lsk,87848
matplotlib/__init__.py,sha256=pWjrFCjP1acCOKaKs7AMHt1xVBiNQ_EXQtq6DUJJQUU,64067
matplotlib/__pycache__/__init__.cpython-36.pyc,,
matplotlib/__pycache__/_animation_data.cpython-36.pyc,,
matplotlib/__pycache__/_cm.cpython-36.pyc,,
matplotlib/__pycache__/_cm_listed.cpython-36.pyc,,
matplotlib/__pycache__/_color_data.cpython-36.pyc,,
matplotlib/__pycache__/_constrained_layout.cpython-36.pyc,,
matplotlib/__pycache__/_layoutbox.cpython-36.pyc,,
matplotlib/__pycache__/_mathtext_data.cpython-36.pyc,,
matplotlib/__pycache__/_pylab_helpers.cpython-36.pyc,,
matplotlib/__pycache__/_version.cpython-36.pyc,,
matplotlib/__pycache__/afm.cpython-36.pyc,,
matplotlib/__pycache__/animation.cpython-36.pyc,,
matplotlib/__pycache__/artist.cpython-36.pyc,,
matplotlib/__pycache__/axis.cpython-36.pyc,,
matplotlib/__pycache__/backend_bases.cpython-36.pyc,,
matplotlib/__pycache__/backend_managers.cpython-36.pyc,,
matplotlib/__pycache__/backend_tools.cpython-36.pyc,,
matplotlib/__pycache__/bezier.cpython-36.pyc,,
matplotlib/__pycache__/blocking_input.cpython-36.pyc,,
matplotlib/__pycache__/category.cpython-36.pyc,,
matplotlib/__pycache__/cm.cpython-36.pyc,,
matplotlib/__pycache__/collections.cpython-36.pyc,,
matplotlib/__pycache__/colorbar.cpython-36.pyc,,
matplotlib/__pycache__/colors.cpython-36.pyc,,
matplotlib/__pycache__/container.cpython-36.pyc,,
matplotlib/__pycache__/contour.cpython-36.pyc,,
matplotlib/__pycache__/dates.cpython-36.pyc,,
matplotlib/__pycache__/docstring.cpython-36.pyc,,
matplotlib/__pycache__/dviread.cpython-36.pyc,,
matplotlib/__pycache__/figure.cpython-36.pyc,,
matplotlib/__pycache__/font_manager.cpython-36.pyc,,
matplotlib/__pycache__/fontconfig_pattern.cpython-36.pyc,,
matplotlib/__pycache__/gridspec.cpython-36.pyc,,
matplotlib/__pycache__/hatch.cpython-36.pyc,,
matplotlib/__pycache__/image.cpython-36.pyc,,
matplotlib/__pycache__/legend.cpython-36.pyc,,
matplotlib/__pycache__/legend_handler.cpython-36.pyc,,
matplotlib/__pycache__/lines.cpython-36.pyc,,
matplotlib/__pycache__/markers.cpython-36.pyc,,
matplotlib/__pycache__/mathtext.cpython-36.pyc,,
matplotlib/__pycache__/mlab.cpython-36.pyc,,
matplotlib/__pycache__/offsetbox.cpython-36.pyc,,
matplotlib/__pycache__/patches.cpython-36.pyc,,
matplotlib/__pycache__/path.cpython-36.pyc,,
matplotlib/__pycache__/patheffects.cpython-36.pyc,,
matplotlib/__pycache__/pylab.cpython-36.pyc,,
matplotlib/__pycache__/pyplot.cpython-36.pyc,,
matplotlib/__pycache__/quiver.cpython-36.pyc,,
matplotlib/__pycache__/rcsetup.cpython-36.pyc,,
matplotlib/__pycache__/sankey.cpython-36.pyc,,
matplotlib/__pycache__/scale.cpython-36.pyc,,
matplotlib/__pycache__/spines.cpython-36.pyc,,
matplotlib/__pycache__/stackplot.cpython-36.pyc,,
matplotlib/__pycache__/streamplot.cpython-36.pyc,,
matplotlib/__pycache__/table.cpython-36.pyc,,
matplotlib/__pycache__/texmanager.cpython-36.pyc,,
matplotlib/__pycache__/text.cpython-36.pyc,,
matplotlib/__pycache__/textpath.cpython-36.pyc,,
matplotlib/__pycache__/ticker.cpython-36.pyc,,
matplotlib/__pycache__/tight_bbox.cpython-36.pyc,,
matplotlib/__pycache__/tight_layout.cpython-36.pyc,,
matplotlib/__pycache__/transforms.cpython-36.pyc,,
matplotlib/__pycache__/type1font.cpython-36.pyc,,
matplotlib/__pycache__/units.cpython-36.pyc,,
matplotlib/__pycache__/widgets.cpython-36.pyc,,
matplotlib/_animation_data.py,sha256=CCdf8YwNX_08FS3YFPYzr2wi3id_WXIKLHrqM50HM8g,6157
matplotlib/_cm.py,sha256=C3xi_H7o8WF6qSv3Jl0DA1T2vbT-4wIDLFYfuJe5Zpg,66609
matplotlib/_cm_listed.py,sha256=EpTjQ6pZ9E_UeY4kDa6fU9za_VHBvhuOmCG6fwNRvlk,98362
matplotlib/_color_data.py,sha256=9hUzbyqLpEe-2LjEeAN3ja2ANuryNvtTseU8vuJXfsI,34776
matplotlib/_constrained_layout.py,sha256=1ygKBGfY0ttpPFhEWucL0wVnwYP-U_ZX8OxHlCUXXjQ,29304
matplotlib/_contour.cpython-36m-x86_64-linux-gnu.so,sha256=Gd6D_VYbA-imr5k186kroDG91DrOUYiltJS3QuARruQ,95144
matplotlib/_image.cpython-36m-x86_64-linux-gnu.so,sha256=akH-urj3-vOY9iYC-N9lORYlpSVVMPUKMBnCAVPrqc0,242496
matplotlib/_layoutbox.py,sha256=yXrJA2hBYKdzPNzbrsLl0Lv8zAPtnPulQ9-BbfhcVWM,24354
matplotlib/_mathtext_data.py,sha256=CmKFRW6mXCJqgZSQaiNOSG_VUn9WiSx5Hrg-4qKIn14,89371
matplotlib/_path.cpython-36m-x86_64-linux-gnu.so,sha256=jEhQzvD031IYQRSbo4Fxobi5KpZF-NV_7iuDDRaVBDU,190216
matplotlib/_png.cpython-36m-x86_64-linux-gnu.so,sha256=DlT39C99TemBT-i-7MuJuwJRg6eVwRhMMR4AxInU2QI,48144
matplotlib/_pylab_helpers.py,sha256=lC5IY7NOGCPVD5brJj-PdtJkzA53Fs4hrn9exdzWDFg,3528
matplotlib/_qhull.cpython-36m-x86_64-linux-gnu.so,sha256=dpM763p27zLpJIW8u045wQW71Oz3JwP9BHCHW6M-pHY,382640
matplotlib/_tri.cpython-36m-x86_64-linux-gnu.so,sha256=7InLl8WYUN4CyAtbrAWCXzycxTUs7_xNZx4QZKcifZg,128616
matplotlib/_version.py,sha256=bHCJPWh4TT1eI2_VgNQWyLUAJMklrvz_FkivkMGVXmk,471
matplotlib/afm.py,sha256=Cfj2v5Rgsr5GaDzuKySLd-aqsBdJBLX2T1gt7TQ175o,16934
matplotlib/animation.py,sha256=WyFTZBvyIN2tmvnT9LzlGZDOvqWgPn0SHOfzl0e1LlY,67680
matplotlib/artist.py,sha256=VbAnAHa4CPr39s_99S0yAD9-tBUMPxc-BLvhWeykSLk,48885
matplotlib/axes/__init__.py,sha256=npQuBvs_xEBEGUP2-BBZzCrelsAQYgB1U96kSZTSWIs,46
matplotlib/axes/__pycache__/__init__.cpython-36.pyc,,
matplotlib/axes/__pycache__/_axes.cpython-36.pyc,,
matplotlib/axes/__pycache__/_base.cpython-36.pyc,,
matplotlib/axes/__pycache__/_subplots.cpython-36.pyc,,
matplotlib/axes/_axes.py,sha256=o_hBMnccGhT_BedP7n12Kk6Y-qhEmL7S3guI5EF7r_M,306901
matplotlib/axes/_base.py,sha256=-gd9r0IAp785gRHpHpaN1IibgC5J069NNPKH2d_UbHQ,156195
matplotlib/axes/_subplots.py,sha256=pPG7GjDBFGDnjVXdojw3_ROHUO5kjZM2s7FsUwIH7sE,9479
matplotlib/axis.py,sha256=s2rcQiyNOCskQ3e4J9YeG-dI0GMbH8v3SWbrC7ymtPo,90811
matplotlib/backend_bases.py,sha256=7EaSuF58KKjm5sRYnmsUECkSJc0BcbsYmGd3gMpcpJM,111487
matplotlib/backend_managers.py,sha256=R3ZGS7NiyfAYutPX9KRqRXiZur8VVFb2_yQPNKtQ2Os,12983
matplotlib/backend_tools.py,sha256=nCruryzfTxOT0v8wyQPzPvCuNdOx5eWXTiiVniGoQmg,35810
matplotlib/backends/__init__.py,sha256=Y2fIYrWuxw3DsTGBgaYTkoExgiv5V-fpFMoL6Xluv1U,3593
matplotlib/backends/__pycache__/__init__.cpython-36.pyc,,
matplotlib/backends/__pycache__/_backend_tk.cpython-36.pyc,,
matplotlib/backends/__pycache__/_gtk3_compat.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_gtk3.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_gtk3agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_macosx.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_mixed.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_nbagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_pdf.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_pgf.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_ps.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt4.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt4agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt4cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt5.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt5agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt5cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_svg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_template.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_tkagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_tkcairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_webagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_webagg_core.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_wx.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_wxagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_wxcairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/qt_compat.cpython-36.pyc,,
matplotlib/backends/__pycache__/tkagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/windowing.cpython-36.pyc,,
matplotlib/backends/__pycache__/wx_compat.cpython-36.pyc,,
matplotlib/backends/_backend_agg.cpython-36m-x86_64-linux-gnu.so,sha256=7egU7A_XSGu5zAhXxcs7ubTrUwDx9wCah09ET3u9BTg,358384
matplotlib/backends/_backend_tk.py,sha256=oG6iWxbo96fmE24Q1-2p62MX2sSXwp-bDsCiteqw7bw,37878
matplotlib/backends/_gtk3_compat.py,sha256=_mQjgaLq7PAdWCmkYkN3ZEc7SFms4T47Y6X1bmc3UlI,1458
matplotlib/backends/_tkagg.cpython-36m-x86_64-linux-gnu.so,sha256=ghIFRXt4_3htN74rENcBbpBc_Ly2pRg94F6h_xxE7vI,29080
matplotlib/backends/backend_agg.py,sha256=oWgXuSgkOr_w_Ix3xB07QwaW34CIrDeFr3Ar38KWLnI,21136
matplotlib/backends/backend_cairo.py,sha256=F_Er53Cssgiaz22qKUExxLvgBssxmsl0vRTenkckDJA,23049
matplotlib/backends/backend_gtk3.py,sha256=7yldMkTpLnLqd9GLi905Ba2Se0gBe2bNNjrojNYDKMM,34061
matplotlib/backends/backend_gtk3agg.py,sha256=Sdv_sVpJab05fFqZEz4a8hv2jB6194l0S7I9kioDCBc,2863
matplotlib/backends/backend_gtk3cairo.py,sha256=qvEc-eOfNhYhvG_4COnmCt3e_L-dInGMMnWJY3EG1s8,1516
matplotlib/backends/backend_macosx.py,sha256=cQLOUeHfteu3UzK5VhdBwNRohFidcNGsJ9NNCpcx52Y,6534
matplotlib/backends/backend_mixed.py,sha256=w0npmW09OKYhaHlpaKXY9vBhKNpUrL_gplxhjlm_re0,5738
matplotlib/backends/backend_nbagg.py,sha256=981WrP82fEZJIdND77C2JW4DpS3k82J6PyXthy18-jM,8707
matplotlib/backends/backend_pdf.py,sha256=L6GwqJKiPaFlqPRuhFbJQ9xGhZf8DGkL-FQ5W_nSamw,96965
matplotlib/backends/backend_pgf.py,sha256=8a9VSoU32ZatMBik36IjmrM56GkU0QFc3w1o3z3y89w,43189
matplotlib/backends/backend_ps.py,sha256=M47hHy6DgqRev1emHxv-K8diIoP5aYsNKW1sMex2h2A,61117
matplotlib/backends/backend_qt4.py,sha256=x9KHRXMxJfmlJ-6lMdmKrw9cGe5RlZAAGjk1Ga1xX2c,410
matplotlib/backends/backend_qt4agg.py,sha256=xTZMUL161hqcsOKSeU4sEs9kYloQrC7_OJjfyuYUAp0,245
matplotlib/backends/backend_qt4cairo.py,sha256=B-LD_AECxVVsZA6Zb-oxoN39iM-GUNl81LR7nVaJ8q4,159
matplotlib/backends/backend_qt5.py,sha256=HTxAS2-cOPD_pS40BP4foUl-UfQb3XHmQrNJsuWE8Vs,41475
matplotlib/backends/backend_qt5agg.py,sha256=plgjHJqJ0IawKuEH8Re_mTOLLrH90mRcMs65nbdTLvE,3237
matplotlib/backends/backend_qt5cairo.py,sha256=wTVbxWW9f5REOesXDIzI2eSmE5HS-nwWNcg2m-YvyLI,1977
matplotlib/backends/backend_svg.py,sha256=I_-tR0sEpRFt7jlM8S-dJX8vgjwMifEJTa5N3maJWC0,45518
matplotlib/backends/backend_template.py,sha256=drJi5J7r4ZMpmwvYiVp3uVfWDJ0VtZSEd2A4OD93fUU,9149
matplotlib/backends/backend_tkagg.py,sha256=WMslLWYmtxlmAaBH4tx4HjmRDWMKiSV91KHF9yeMRng,676
matplotlib/backends/backend_tkcairo.py,sha256=dVCh7ZD_2OR0DBQ0N3icD8cDV1SeEzCsRja446wWhPw,1069
matplotlib/backends/backend_webagg.py,sha256=kqDsqw1a0mZA3-ouBGegg8BlWys5iAA0-fAtxTvUZ5o,11124
matplotlib/backends/backend_webagg_core.py,sha256=c8FqOMph-FdYJ-kNauNG23enXWwZwbmQ3QzjhYud4IM,17554
matplotlib/backends/backend_wx.py,sha256=gQTIV29utVqq_W6iqRE5gqGwhDZ3sPg2s0fxXDkmcCY,74559
matplotlib/backends/backend_wxagg.py,sha256=TGdhDULgRzqhLNOzq7QAt0oe_AqHoT-IyZOO8RlE4Bw,4015
matplotlib/backends/backend_wxcairo.py,sha256=VC5TyJaX8TPLSgHv5ckAreoGrY_KiNRMQjVInMLlcFk,1843
matplotlib/backends/qt_compat.py,sha256=gdDU16Oe1HfwJJSGmvLvEJmrKDX6T8Tt1WiSUITR3qU,6658
matplotlib/backends/qt_editor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
matplotlib/backends/qt_editor/__pycache__/__init__.cpython-36.pyc,,
matplotlib/backends/qt_editor/__pycache__/figureoptions.cpython-36.pyc,,
matplotlib/backends/qt_editor/__pycache__/formlayout.cpython-36.pyc,,
matplotlib/backends/qt_editor/__pycache__/formsubplottool.cpython-36.pyc,,
matplotlib/backends/qt_editor/figureoptions.py,sha256=_TD7NJY_LzXDoljckLqKaVBYa3FSJwEFNrccYpbKMTM,9133
matplotlib/backends/qt_editor/formlayout.py,sha256=qCAmYnZUEwgBRMVsPWuu7e1fqCU0OSM-I5Snw7ShcBI,19573
matplotlib/backends/qt_editor/formsubplottool.py,sha256=HiiXkwCotra_hI9JU208KOs8Q9JuGH1uAW3mV5l3Evg,1934
matplotlib/backends/tkagg.py,sha256=ro4lL5U0cLMslYmLXQRhWwR1mxlcZYGO9awistZzL1E,1319
matplotlib/backends/web_backend/all_figures.html,sha256=GiIHkdjLO94c_GAHVX4Zk5R88uEnIUwW2CJgm3qCCv0,1512
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/css/themes/base/images/ui-bg_diagonals-thick_18_b81900_40x40.png,sha256=xRM8xoz-7ahUtdsImL_ZC6s8kJT2QnE04-cji0ZQfsE,418
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_diagonals-thick_20_666666_40x40.png,sha256=T9PQekCQeDFHwAxdZMQs_QzAqaqtimo0uLCotJ0xSqg,312
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_flat_10_000000_40x100.png,sha256=r1CSqnKXLAw-qLRMm5LbIAnqQWB3OLzAqdpZesaoc-o,205
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_100_f6f6f6_1x400.png,sha256=_ws_N6fYaeQspm2VHS5Wm-QV4CXhlUoSqGQ-lmW3xm0,262
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_100_fdf5ce_1x400.png,sha256=-VfVTyNvfByi7t0zq33BBb1mLO5iuIBvsso4Ye2ysXw,348
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png,sha256=JFJf1ebnJmze7uXUNtoLcgn-dTh-bJxVxXVLIuQDGrU,207
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_gloss-wave_35_f6a828_500x100.png,sha256=Av4RPyWlnMm1at-9VqlutTAvTKW7637sDlQamg2ozNA,5815
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_highlight-soft_100_eeeeee_1x100.png,sha256=80rH2tcJybpprH1zkHIN1U_aVhUcZOc9mv9OEYavhRA,278
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_highlight-soft_75_ffe45c_1x100.png,sha256=JRY-0684rYlKOQKRQmXYJ5YiPhHS3kNPZmUVa3xi3hg,328
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_222222_256x240.png,sha256=_htyYBLdV3XU9kp9QnMKIQ8pBX6OgU8zkE05EsTZq9s,6922
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_228ef1_256x240.png,sha256=Z8eq2yeIM45jXe3uMbiKqOqQjA2MKR20tWcBTVc6bC8,4549
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ef8c08_256x240.png,sha256=loEi-IH5oyL8PSSjAGPT549tcpGfRzIbFEaqPFhR8ck,4549
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ffd27a_256x240.png,sha256=O-dEGxrQchuZxTdpQJQ4LEirQLT1OA5lBNYO9O8mJo8,4549
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ffffff_256x240.png,sha256=sVicDAn7JIIW-osNHhgSqa-bSRWOtS4G94BitP_MAds,6299
matplotlib/backends/web_backend/jquery/css/themes/base/jquery-ui.css,sha256=zs9cWf98KIv5DMYiF1a9lhJGQwhVe5LKVPJ9HNEI880,35348
matplotlib/backends/web_backend/jquery/css/themes/base/jquery-ui.min.css,sha256=VQzrlVm7QjdSeQn_IecZgE9rnfM390H3VoIcDJljOSs,30163
matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js,sha256=kaIBSZlozqJ4IeqPwOlDOi97qhNHWTchpUedwo5-gMU,284395
matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.min.js,sha256=7LkWEzqTdpEfELxcZZlS6wAx5Ff13zZ83lYO2_ujj7g,95957
matplotlib/backends/web_backend/jquery/js/jquery-ui.js,sha256=DI6NdAhhFRnO2k51mumYeDShet3I8AKCQf_tf7ARNhI,470596
matplotlib/backends/web_backend/jquery/js/jquery-ui.min.js,sha256=xNjb53_rY-WmG-4L6tTl9m6PpqknWZvRt0rO1SRnJzw,240427
matplotlib/backends/web_backend/js/mpl.js,sha256=SqYBZRsHYoQY9d-5eCZsVpIBnlR4T7t1JiYYSZOs6Uw,16964
matplotlib/backends/web_backend/js/mpl_tornado.js,sha256=lSxC7-yqF1GYY-6SheaHanx6SujMdcG7Vx2_3qbi-9Q,272
matplotlib/backends/web_backend/js/nbagg_mpl.js,sha256=WfV96-6LXzUSnzCmvQ93JopqO7KPqnfbT_07Q1XJeT4,7467
matplotlib/backends/web_backend/nbagg_uat.ipynb,sha256=y1N8hQzBJ05rJ2hZla2_Mw6tOUfNP1UHKo636W1e098,15933
matplotlib/backends/web_backend/single_figure.html,sha256=56UAOD6qgZ-T6wE2EjGpTBG4iwAmfG3QsnX5lLQp2dI,1203
matplotlib/backends/windowing.py,sha256=q-hSo_vvsjst6EjNkr94NIDbUucMocrMj9MSjVwkfY4,822
matplotlib/backends/wx_compat.py,sha256=RQFVDMaMLjT8YmazLiq3BcjCcCi6xuzS_AwmxtYd2vg,968
matplotlib/bezier.py,sha256=zwC07cDX9iyh9KWnZsPBQaFJ4CTue0NQpm1VwS2PEuM,15415
matplotlib/blocking_input.py,sha256=E6r1m5acvx_KLDp7rZXftnA5OBtJnjy_zGWBUO9k_OA,11112
matplotlib/category.py,sha256=AWYm1nucRySTh-6cXN2FeNYQOpwJPeUl9iFkjcr4oW0,5928
matplotlib/cbook/__init__.py,sha256=bFrhSeVf8Ya6pNEtkqOTVABSR--ADZLbTlsLqd3BUV4,65667
matplotlib/cbook/__pycache__/__init__.cpython-36.pyc,,
matplotlib/cbook/__pycache__/deprecation.cpython-36.pyc,,
matplotlib/cbook/deprecation.py,sha256=5ghVk2E7syukLQuADs7UtHuBRJYaiuvylSymAh_axDQ,9402
matplotlib/cm.py,sha256=MHQtLJ9UGDuyxHa-qyMxL2vikkhXB5OXhYdxUy-_68M,12843
matplotlib/collections.py,sha256=HqR0G7ydei6YkX06MYwp0TGrVZaXz7BDNiwEPGk0Fro,66851
matplotlib/colorbar.py,sha256=_UImtSk1sRHbLGNn_gLTy86mFYqzjovJMvioqcvn57w,59164
matplotlib/colors.py,sha256=HeZGu5MwSkMLEABhU5qYmJw4ZbvpxeOrdV2ytY_klSg,71636
matplotlib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
matplotlib/compat/__pycache__/__init__.cpython-36.pyc,,
matplotlib/compat/__pycache__/subprocess.cpython-36.pyc,,
matplotlib/compat/subprocess.py,sha256=-K36IW-t5SjjWcP4nL80GAE-y7v0HZE6nQ-hscwmkvg,1562
matplotlib/container.py,sha256=Sbz7JTBAFirjClv-nSpVojuSLKIBoUo-dAve8QAFjl8,5245
matplotlib/contour.py,sha256=XiEg_unL8WQaQf3f3c41KBHltZPXJ8Fhk2W0OGvuacc,70231
matplotlib/dates.py,sha256=XsBhin-wZcY7FPcMrDQDBEYjYXNtmc-YbtwZiysKeqQ,61547
matplotlib/docstring.py,sha256=M5wraMzJf7veSfzUJUArSY68bM5lkH3q6niSmW0q1xw,3787
matplotlib/dviread.py,sha256=s_N3IU68PIVm03TFnBP8_UakGnIYwYCv1vxLDjpWsk4,37282
matplotlib/figure.py,sha256=p9yke2KjrUkOJ5ZUt-q44GqMjCU5woMnFMAXLR3-DJk,95506
matplotlib/font_manager.py,sha256=uWPJ8AR9gzeIljZz8baIRTDacgsAMORLgmV1lT6Ve_s,45972
matplotlib/fontconfig_pattern.py,sha256=LYKBekGIwiHNYyYe36Lfki4JyEkZMsBinmhVzBc1wns,6593
matplotlib/ft2font.cpython-36m-x86_64-linux-gnu.so,sha256=xKwvjhkVt1lLcRA4Swcz9iHqhWTk78UGl6CLyR5tgvw,894456
matplotlib/gridspec.py,sha256=C5rAu1tokWgaegWBEocG6phEfemgMF9dch-HwVfKC0A,20056
matplotlib/hatch.py,sha256=kmq09LW_AJxPY0LGJxJrrC9Yf6tPQYi209EMAveqQoE,6988
matplotlib/image.py,sha256=LGgYY2UmfjldiGhqAU6Qwi-3I_CJAzbL6b9hFMhCWHQ,54632
matplotlib/legend.py,sha256=M6PCguGj32SpnEuLvPhRKfWZuCofn1JYSubhRb9e5Yk,48770
matplotlib/legend_handler.py,sha256=gmQCUHjMmvcJ4g8dSqayhaXj82jkUK36i0-4DK6ik-A,25524
matplotlib/lines.py,sha256=uKymslFnSniquZR_5N266mXvkQJ_4eKu_sg5iP5t1rA,48503
matplotlib/markers.py,sha256=yILg87-gXelLwbu2o1xHNdCikXVHG50frJQJ6ywY8OY,34345
matplotlib/mathtext.py,sha256=vXlAVF9T7xA7-uRHnBu4T6LBZhr97a6obWEAr-X-mA4,121307
matplotlib/mlab.py,sha256=mhkOI6_Wgw0aaaqPDuCE53tHLNxl8tj_ajgoCZbRUcY,124056
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/fonts/ttf/local.conf,sha256=wcg11V6oGPKn4c43Z_bGGo9TCyvjMWq9msuzMmdtRTo,988
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.ppm,sha256=voTyfqUGvirNbkrsJGMwJT0z0g_KFWAnO8Hn8J4XXh8,1741
matplotlib/mpl-data/images/matplotlib.svg,sha256=QiTIcqlQwGaVPtHsEk-vtmJk1wxwZSvijhqBe_b9VCI,62087
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=-CfOXSRZ7WaduxxmjJjX-_PLETPwuzleTpGhTLd7ylU,33061
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/stylelib/Solarize_Light2.mplstyle,sha256=PECeO60wwJe2sSDvxapBJRuKGek0qLcoaN8qOX6tgNQ,1255
matplotlib/mpl-data/stylelib/_classic_test.mplstyle,sha256=XnegNNz-4tr8vnTgI1IakyHYPryuInJD1GidF9a8n6E,25458
matplotlib/mpl-data/stylelib/bmh.mplstyle,sha256=-KbhaI859BITHIoyUZIfpQDjfckgLAlDAS_ydKsm6mc,712
matplotlib/mpl-data/stylelib/classic.mplstyle,sha256=brJE6RZ114bTIVwl7yBm2sd6s0TyRrUq9t-qi--Ih20,25639
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=KZdfpLd6rYRjmRAboaRBuKag8lDUCzMEeykgmp4irx8,55449
matplotlib/patches.py,sha256=wUAZISlGrUWm_bxDh88ryGoWkOXrJZttyanOnZWdDQ4,151034
matplotlib/path.py,sha256=Ys7EYJOyCTAQm0fROCvHdgg2Kxp5AZwkJhG42hw3QZc,37139
matplotlib/patheffects.py,sha256=l7huZDCPG8RS3LLq67EztQg7s64zngoUN1R60zxpp5k,14139
matplotlib/projections/__init__.py,sha256=-_LPKYaHOdfgxYdCtPs-nDJvjQ8ZTXIuAcC0v_1_zFY,2874
matplotlib/projections/__pycache__/__init__.cpython-36.pyc,,
matplotlib/projections/__pycache__/geo.cpython-36.pyc,,
matplotlib/projections/__pycache__/polar.cpython-36.pyc,,
matplotlib/projections/geo.py,sha256=EI7YSw7UbZTsb31dCTAH0aJRXQct-cOnm45alvAj3Cc,18909
matplotlib/projections/polar.py,sha256=6X8t4p4LAx8h7O7WJ-T5SDhbKaNsA8X6dcXIl4z4wgM,55006
matplotlib/pylab.py,sha256=lNqCpuoTGfjprzltmvpQ3DYthdj_FJf6PRFfeEv3HhY,10331
matplotlib/pyplot.py,sha256=5AwtjpRSBFNyRyKTVrWIWSl_EPVPyPQ6RpZdOidI1nI,110436
matplotlib/quiver.py,sha256=9eP6xfF6pOOQpQ3zxbvedZewUYE236OS9gwRNZhPvws,45910
matplotlib/rcsetup.py,sha256=aFy_3zYioc0oCEmRdvqIGlCRbSA6hj7BKKTO-k2__Gw,58203
matplotlib/sankey.py,sha256=tMlZvy0CmG55hpDVY6Dqk461x6vvSxvhDmkIXJG_qDo,38734
matplotlib/scale.py,sha256=l9r0dmYGeMCLkqcrcJRIBFj6tJq6Znvc9ZoRTynHLoI,19164
matplotlib/sphinxext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
matplotlib/sphinxext/__pycache__/__init__.cpython-36.pyc,,
matplotlib/sphinxext/__pycache__/mathmpl.cpython-36.pyc,,
matplotlib/sphinxext/__pycache__/plot_directive.cpython-36.pyc,,
matplotlib/sphinxext/mathmpl.py,sha256=sdWYQ5aBB9hGiEY5E3j9By-DaFENDNHtTHxLMRQXrWc,3919
matplotlib/sphinxext/plot_directive.py,sha256=uMBuVvlmjuLUr6r1iDm42L1yN7A1r_knUXrcfxPtTBU,27082
matplotlib/sphinxext/tests/__init__.py,sha256=gTnBimTaMh3t3WC49SvyeSH0rsbWxQgjAsr5H8HRDig,23
matplotlib/sphinxext/tests/__pycache__/__init__.cpython-36.pyc,,
matplotlib/sphinxext/tests/__pycache__/conftest.cpython-36.pyc,,
matplotlib/sphinxext/tests/__pycache__/test_tinypages.cpython-36.pyc,,
matplotlib/sphinxext/tests/conftest.py,sha256=Ph6QZKdfAnkPwU52StddC-uwtCHfANKX1dDXgtX122g,213
matplotlib/sphinxext/tests/test_tinypages.py,sha256=uU72lyon36bSL2xF_90zfkblEolK_Cj5VGrfEpv0_wU,2058
matplotlib/sphinxext/tests/tinypages/__pycache__/conf.cpython-36.pyc,,
matplotlib/sphinxext/tests/tinypages/__pycache__/range4.cpython-36.pyc,,
matplotlib/sphinxext/tests/tinypages/__pycache__/range6.cpython-36.pyc,,
matplotlib/sphinxext/tests/tinypages/_static/README.txt,sha256=1nnoizmUuHn5GKx8RL6MwJPlkyGmu_KHhYIMTDSWUNM,303
matplotlib/sphinxext/tests/tinypages/conf.py,sha256=0_a4wyqPA9oaOFpLLpSEzkZI-hwtyRbqLWBx9nf0sLA,8432
matplotlib/sphinxext/tests/tinypages/index.rst,sha256=kLSy7c3SoIAVsKOFkbzB4zFVzk3HW3d_rJHlHcNGBAg,446
matplotlib/sphinxext/tests/tinypages/range4.py,sha256=fs2krzi9sY9ysmJRQCdGs_Jh1L9vDXDrNse7c0aX_Rw,81
matplotlib/sphinxext/tests/tinypages/range6.py,sha256=a2EaHrNwXz4GJqhRuc7luqRpt7sqLKhTKeid9Drt2QQ,281
matplotlib/sphinxext/tests/tinypages/some_plots.rst,sha256=C9rwV9UVlhFvxm8VqV6PoAP1AQ8Kk0LGZI9va4joif0,2156
matplotlib/spines.py,sha256=dctV_-aWkJuc52EgZof2aPjcelWVKOXttz66sZyrPkk,21217
matplotlib/stackplot.py,sha256=4YbKAU349muVPKAkNSdt9r0tG_hcHj5iCy1Dp9otWGA,3977
matplotlib/streamplot.py,sha256=_ARll6zMPC0-eLPBW-svHOs4wcG8W2tTABIcFWVwN3I,21933
matplotlib/style/__init__.py,sha256=EExOAUAq3u_rscUwkfKtZoEgLA5npmltCrYZOP9ftjw,67
matplotlib/style/__pycache__/__init__.cpython-36.pyc,,
matplotlib/style/__pycache__/core.cpython-36.pyc,,
matplotlib/style/core.py,sha256=8LwOz9KtNuYLOzYiUyeUJNN_w-y_LsAGRhTN_SvCftA,7901
matplotlib/table.py,sha256=fp3kcp88hOthPbc02LwZbnvbQ3__vne_LpOk2Bw_e2o,21917
matplotlib/testing/__init__.py,sha256=z-NqrY_YBuiQGl4QVqRYAGOZcyGnSIcI16XjAbmIsjM,1498
matplotlib/testing/__pycache__/__init__.cpython-36.pyc,,
matplotlib/testing/__pycache__/compare.cpython-36.pyc,,
matplotlib/testing/__pycache__/conftest.cpython-36.pyc,,
matplotlib/testing/__pycache__/decorators.cpython-36.pyc,,
matplotlib/testing/__pycache__/determinism.cpython-36.pyc,,
matplotlib/testing/__pycache__/disable_internet.cpython-36.pyc,,
matplotlib/testing/__pycache__/exceptions.cpython-36.pyc,,
matplotlib/testing/compare.py,sha256=vklvca9q772lqZ7wsaHxI2n3MY65MCHnKTjFKV48R6s,17552
matplotlib/testing/conftest.py,sha256=NYJUijf2Rm25TwLysGfCNFKPEtD_zK9sWG5lcfUZS58,3253
matplotlib/testing/decorators.py,sha256=zOMJfeD75zD8OA5MrxsRCTOLiDGlZaMERNGw5LyBrN8,17704
matplotlib/testing/determinism.py,sha256=4GADMpjbO3127YtWNK2bNB2HTUWUkyEEQB397LGCpB0,4389
matplotlib/testing/disable_internet.py,sha256=YE5szJX8tNHyK8j90uPUOJO76MmUHKrXFqRnj_xGyu0,4747
matplotlib/testing/exceptions.py,sha256=72QmjiHG7DwxSvlJf8mei-hRit5AH3NKh0-osBo4YbY,138
matplotlib/testing/jpl_units/Duration.py,sha256=FDR6rn_Yvc3XlfLuJe4KI6ALMSJCytZGZX5xIQz-yuc,6875
matplotlib/testing/jpl_units/Epoch.py,sha256=1Htkm5XtuToVC5otGBWgsOlhPVH4FcV8EA9CkNHNwI8,7859
matplotlib/testing/jpl_units/EpochConverter.py,sha256=NbxO32pLLg7hMuiiB2QiAuiipGAfT6q2tSSf9Xys26o,5424
matplotlib/testing/jpl_units/StrConverter.py,sha256=g2EEkhg4ZdT8PSB-4MjPDNRbilRp7Wi72mTnsv7Ty7g,5295
matplotlib/testing/jpl_units/UnitDbl.py,sha256=b_4G6NaHJUl4Xd-NE1BZzombIHyOGO0i6Vb0MQGBeuw,9725
matplotlib/testing/jpl_units/UnitDblConverter.py,sha256=9AIYgnnR78G0E0D3rI20IgTawiCYC2bA_izCmy5csNo,5419
matplotlib/testing/jpl_units/UnitDblFormatter.py,sha256=bE8adtYRXG5gzQObrzR-ROLwkFkpru7GDjjUIlRh7Ss,1416
matplotlib/testing/jpl_units/__init__.py,sha256=fWVROJbodccLPtdnFzhV8ItE1Dl1uinQc9HcYz4hmpE,3062
matplotlib/testing/jpl_units/__pycache__/Duration.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/Epoch.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/EpochConverter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/StrConverter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/UnitDbl.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/UnitDblConverter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/UnitDblFormatter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/__init__.cpython-36.pyc,,
matplotlib/tests/__init__.py,sha256=g3AQvrbsKOFFnkrCoQzlqr1cXCDV0LvGsPnvPhNOrYs,463
matplotlib/tests/__pycache__/__init__.cpython-36.pyc,,
matplotlib/tests/__pycache__/conftest.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_afm.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_agg.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_animation.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_arrow_patches.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_artist.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_axes.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_bases.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_nbagg.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_pdf.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_pgf.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_ps.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_qt4.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_qt5.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_svg.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_tools.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backends_interactive.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_basic.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_bbox_tight.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_category.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_cbook.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_collections.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_colorbar.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_colors.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_compare_images.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_constrainedlayout.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_container.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_contour.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_cycles.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_dates.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_dviread.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_figure.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_font_manager.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_gridspec.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_image.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_legend.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_lines.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_marker.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_mathtext.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_matplotlib.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_mlab.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_offsetbox.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_patches.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_path.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_patheffects.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_pickle.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_png.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_preprocess_data.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_pyplot.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_quiver.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_rcparams.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_sankey.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_scale.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_simplification.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_skew.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_spines.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_streamplot.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_style.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_subplots.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_table.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_texmanager.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_text.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_ticker.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_tightlayout.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_transforms.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_triangulation.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_ttconv.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_type1font.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_units.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_usetex.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_widgets.cpython-36.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=64Qvm_dkFOh88o8oBouswePl1kgSUE_37jrRN0__jng,2218
matplotlib/tests/test_agg.py,sha256=aorEpO-NshLjutQQV-yF1ZikLIqss9fF7ouXsjG7o3s,7299
matplotlib/tests/test_animation.py,sha256=rMZwOf3WEVT7xGYDYc2xkx6XjW8_mQ0kFL3wouIaZ0g,8267
matplotlib/tests/test_arrow_patches.py,sha256=dfRDXMnKZt0FBY5JAtjKx8qbCnHCzAQoFhuEy3EzhPg,5950
matplotlib/tests/test_artist.py,sha256=PjaIk-F4jN0QEhPm3ZinWIMOB04LesHnMe0xmLmlm4g,9203
matplotlib/tests/test_axes.py,sha256=QQbwWolkRT1jytrYIp1tzsJN1tbpqF7tL0M4mIAa148,189994
matplotlib/tests/test_backend_bases.py,sha256=_jib8QrQa98qAarlE01aIk7Hkfs1gQbcVDIMGzsVr2U,3585
matplotlib/tests/test_backend_nbagg.py,sha256=TvfgH4gOzFxGRvNohjn1c9vBqEKL4OI6IWwBnZdBWgg,1066
matplotlib/tests/test_backend_pdf.py,sha256=6D7Yah7exx8xFwGsfm5ZcjYcxnRtZs0D8l3z1UZ7qPI,7925
matplotlib/tests/test_backend_pgf.py,sha256=GEYeAku09gn3erePaZMHRWVUBAFwuzdAXmrnFl5a4mc,8157
matplotlib/tests/test_backend_ps.py,sha256=KX7Xak4Yb2UMD6KEiM1-UJTtA5MwbSYhRg3l5gyofWI,4505
matplotlib/tests/test_backend_qt4.py,sha256=gNbJ4BFnwhZ-LGZ9HlXMYtQPsgPAHoYSCs8TDCFmPUA,4199
matplotlib/tests/test_backend_qt5.py,sha256=Pf4B8pNrh8qeh-vKeVeip6_fr2UhCy1fH6HDxLC0Edg,6574
matplotlib/tests/test_backend_svg.py,sha256=fbp1hAcPoxetMsfRdZ50ZttMX-vY1eqSiBfFtCXKB80,5439
matplotlib/tests/test_backend_tools.py,sha256=C-B7NCkyWsQ5KzQEnI5Be16DsAHHZJU9P5v9--wsF-o,501
matplotlib/tests/test_backends_interactive.py,sha256=AuioLNYNgZZ4Q5HJWh58CLsX5CRW0ZJBQrPYZn6DE68,5012
matplotlib/tests/test_basic.py,sha256=jexF5eJw4gwb0hYMx6_u8wJa3hgtwKclag1Lhws_f7U,730
matplotlib/tests/test_bbox_tight.py,sha256=ZcHz4qXTRhiVOiGQgSaqQgA7IjsRh84qDYOoVe82IH4,3280
matplotlib/tests/test_category.py,sha256=HJ1oBEZD0A6wHUnrz49kymv93UX0fkkdFCclIBrI09w,10303
matplotlib/tests/test_cbook.py,sha256=Tm8U0CgUpWthx7xQ_DicpslnOHlXnG9Wr0bO9E-YJAM,15647
matplotlib/tests/test_collections.py,sha256=no4WHkfoo6s6Mlc0hrrd_ssl9bpapGGTMJ7WsvCh8Hg,22100
matplotlib/tests/test_colorbar.py,sha256=YbP1IZp9HftMxpUa_yvVKFJexKCGabXQkZSaPam8qvM,17071
matplotlib/tests/test_colors.py,sha256=JrmZ9kvwN21bUwwa2YMAhrlURLRYDGDAA0yMlOHN5nc,25629
matplotlib/tests/test_compare_images.py,sha256=n3Uoukid0GcjyQpd6ZrqIY9u3RLNE2XAPWwtcHIsqto,3155
matplotlib/tests/test_constrainedlayout.py,sha256=easxoL_jBZe5ChE7eQcjz6aqx4LPxQde9FkRsWoOg5I,13261
matplotlib/tests/test_container.py,sha256=ijZ2QDT_GLdMwKW393K4WU6R7A5jJNaH1uhLRDwoXXI,550
matplotlib/tests/test_contour.py,sha256=2Yl9crBjJegsAEco-mo6oa31TfoJkupLbqlwl9IFkWU,12909
matplotlib/tests/test_cycles.py,sha256=75QI7uIkh4b5ckGd0B7irJuqhIivYx8CmxRAciWRj1g,7490
matplotlib/tests/test_dates.py,sha256=SDos5lnmY_dNNIT-hjVZ3e5OpmSePAOIWFJoA_9tDdM,25085
matplotlib/tests/test_dviread.py,sha256=kTk9Qv6q9Kk3fJcDAEWm35HF-sKsP6Ybec6N8jEHasE,2342
matplotlib/tests/test_figure.py,sha256=LVk5dyf6H8V8Ot2bIQXAnscaqLegeDfud1wtLXusJXo,14204
matplotlib/tests/test_font_manager.py,sha256=8dVBlum-bY9gmy_YVXWERbWtbGNQllwzCyCNxffGCFI,3271
matplotlib/tests/test_gridspec.py,sha256=zahj5Rd4pB0xtAc_3KX7fQWyBys0P-IQk-Cq0cs8VgY,626
matplotlib/tests/test_image.py,sha256=uAMkqy06wYZeJASFKIt-hdWCG310dL-AFp6cnLdnjlM,28263
matplotlib/tests/test_legend.py,sha256=2Q6Qn2C72wRqZvY6Mt9v_udD0bVARDTbWcqBZD1-oCk,19782
matplotlib/tests/test_lines.py,sha256=4n7pdODOXqLvX1zing9SuXXxChPDe7nEr42gaiIG8d8,6221
matplotlib/tests/test_marker.py,sha256=fhHHW93wCl5KbrZRL2iEVziv2BLBZU6zSt66hgsI5jY,739
matplotlib/tests/test_mathtext.py,sha256=3G0W1S2J5QOHs0EfQiAdP5M114sh81sJtDxITrF1gzs,12696
matplotlib/tests/test_matplotlib.py,sha256=DIBqISzUIYanSxNWJL9n2oob1dRLOOAr6TIz2BTWK1I,706
matplotlib/tests/test_mlab.py,sha256=gpd4pJ8fIQNCXdwJV1C1__EKBMdnkvQLVgyFdgNgrZc,96981
matplotlib/tests/test_offsetbox.py,sha256=u_VCL-lWvQ66IGTzuotzRxq23ySzcB86Gjvmeib-S3w,4198
matplotlib/tests/test_patches.py,sha256=IbeCN3mVunvkukneSZKjVbZKGJg0h2oqmOzrI400HQ0,16218
matplotlib/tests/test_path.py,sha256=EwvnGhKmdMFl5w4PC2KWwKVm2qKLuRksM70kb-MUVTU,7164
matplotlib/tests/test_patheffects.py,sha256=Sl90AY4wri37bEvRuS3QEthE_GV5i5drh0Yk2oGJjc0,5372
matplotlib/tests/test_pickle.py,sha256=38XMrLSsH7RgNVesFLB68UAD6HZS7Eylbs3DktWmkBw,5533
matplotlib/tests/test_png.py,sha256=3TieTdSRvSL9mS7bhxpozw1CClQxKYunK83TLPU4bnA,1786
matplotlib/tests/test_preprocess_data.py,sha256=oJuYg8kpqgLrA1wYEUzMLULRoKM3IRf9_ejRzPzM0AU,15990
matplotlib/tests/test_pyplot.py,sha256=wOld704X_pAZVNvnL3WD2KUHliRPtAoqLBh0-OrdeGg,974
matplotlib/tests/test_quiver.py,sha256=hAKbP4S6npdpIfwjw6efhMr6En2A-0fJAjLOiMofxWk,5789
matplotlib/tests/test_rcparams.py,sha256=bmDAFAz3g0ZLHLVqVU9F4POt-X5HbAqpLNfBp4m81N8,20498
matplotlib/tests/test_rcparams.rc,sha256=zwPbYzajd7FTIYURvpwTBAn8i060Do3OVDGZ2xHZeLw,74
matplotlib/tests/test_sankey.py,sha256=ZZBtNqIsFcJLoZTCKSM2xaUfrtMjoSica3Mi-FtMysw,162
matplotlib/tests/test_scale.py,sha256=8V-tt5E79R-P_Zz6e7iH_7KCHU6e1Ql2SLnQdpt7BmQ,4100
matplotlib/tests/test_simplification.py,sha256=c1GSkJwiGxksnUj5WgYbvsr3yDcmbEKHmB_12W_jp7s,10895
matplotlib/tests/test_skew.py,sha256=zOhGb5V-9A531ZpmHlqFsdTL9xfdj2-RL3N7OQJbV20,7058
matplotlib/tests/test_spines.py,sha256=1cN5KequShVG83DggeUxt5QWE9uIkfmyONaoVNJOojw,2326
matplotlib/tests/test_streamplot.py,sha256=F-ilyMfXm47d-XCz6L06GE8KQ-bEHbhKLqIS0EVKv0I,3497
matplotlib/tests/test_style.py,sha256=JAcu_9wDS_Gvtisce9Z6rHrO-SHZVjyPeBDYfKgDZ1c,5333
matplotlib/tests/test_subplots.py,sha256=yMzfFiUpZBcBt2FpLFySHzxmoj2B4jevjLviFWkPouo,5551
matplotlib/tests/test_table.py,sha256=Qoe4Sm-yyog-NOHASdBOjjGQrFQOo2amm01KAgP5MJo,5906
matplotlib/tests/test_texmanager.py,sha256=zCtJ3JnZNfP2AQNy7q2LQAgaflSe7S5htJkJNylQSGE,459
matplotlib/tests/test_text.py,sha256=Vg5HlZU9ob584X5AZAxQuIm6PFj_GO9xnxIBKZU7O2c,15435
matplotlib/tests/test_ticker.py,sha256=OPuJl75xgr_AZ8IDUuyi90PgAXBVT91EnORB2h31mUQ,32802
matplotlib/tests/test_tightlayout.py,sha256=7w_LPOY1EM0Z6i7GPt1--w4z0wmf-5M6rvpd0wauqKg,10016
matplotlib/tests/test_transforms.py,sha256=Y3FWBzEiBBydD5X4id-bgpzRSFCBxyZFtPTbto2KUv0,24713
matplotlib/tests/test_triangulation.py,sha256=XXCtxqhP6jHFe2mbIbB0-KNsdkzxhUDI1V9bDz1A_Eg,44838
matplotlib/tests/test_ttconv.py,sha256=xilgvzZpTpHSnumaUlHvQ_zdIQ7U7xBD1Aflx34I-xU,641
matplotlib/tests/test_type1font.py,sha256=C0pCPBGOv49SR2xxDOq6LSXAEH_ZNvIWvr_jG-23Gmc,2097
matplotlib/tests/test_units.py,sha256=yUnl2ds8QGdJdZWME2YHysQbSr9DG4aEkgIIstMZyMc,5196
matplotlib/tests/test_usetex.py,sha256=9ANPkY6aKfyY0_DopPLivplxlT__v5y5wHX2_8-Z4Z8,918
matplotlib/tests/test_utf32_be_rcparams.rc,sha256=K66jcKehDKcG1yTXJCOSsmp1iteU9Kvsd_eobV5wNW4,56
matplotlib/tests/test_widgets.py,sha256=7sBak0W8XT-NhZ9zNEZC8i_PA6LJIkBgiE_gB42h2cE,16787
matplotlib/texmanager.py,sha256=KBU8HQhgj0DNOTwpCZhH_pJ1IqyfEnF1IgUOBDUpRqQ,17284
matplotlib/text.py,sha256=-q9B-fS_GmCoAEHhEDbY7m9fidyFLjSn3600KAcROkE,81684
matplotlib/textpath.py,sha256=FLg0qVzTVf9_CFbE3O39Pw7-4hiAYiWoqz_dwKMmHYY,17917
matplotlib/ticker.py,sha256=Pb6Eyaiz6ncDoDjE7Vew8h63i1s-x9hNR-aRHP9-fhs,88149
matplotlib/tight_bbox.py,sha256=Yf5X-HVlMe3AqwR5tUJQBH0LLZqOGoh2uP0oJnU6ErA,2463
matplotlib/tight_layout.py,sha256=iQt1J_nr40VPf3GCgqed_r_umsYE5lnvjWxLdh3-uPs,14562
matplotlib/transforms.py,sha256=2pDKJqB7Fnx2WLvWUKY8wG7ry9xk0xtVq9dDu32HvLc,99669
matplotlib/tri/__init__.py,sha256=XMaejh88uov7Neu7MuYMyaNQqaxg49nXaiJfvjifrRM,256
matplotlib/tri/__pycache__/__init__.cpython-36.pyc,,
matplotlib/tri/__pycache__/triangulation.cpython-36.pyc,,
matplotlib/tri/__pycache__/tricontour.cpython-36.pyc,,
matplotlib/tri/__pycache__/trifinder.cpython-36.pyc,,
matplotlib/tri/__pycache__/triinterpolate.cpython-36.pyc,,
matplotlib/tri/__pycache__/tripcolor.cpython-36.pyc,,
matplotlib/tri/__pycache__/triplot.cpython-36.pyc,,
matplotlib/tri/__pycache__/trirefine.cpython-36.pyc,,
matplotlib/tri/__pycache__/tritools.cpython-36.pyc,,
matplotlib/tri/triangulation.py,sha256=YM3AIH44SK4LOJiLPB4Wo-DQ1d2q75SdUQ_W2kaxrO4,8427
matplotlib/tri/tricontour.py,sha256=Uz3bHK7xw3fHnjFstOlurnz9-btm6dfa0lKE7Vt_P0k,9375
matplotlib/tri/trifinder.py,sha256=_S-whwBCe5m9byOzcdAXFJXs0gAIXqy9rVGkXKiM14U,3505
matplotlib/tri/triinterpolate.py,sha256=uWh1PPiaN0nMM30txiEVoAxtFZXvX0hxvPnWzKP3xoc,64969
matplotlib/tri/tripcolor.py,sha256=DwBFSsZ_jBrFKIPrYetMXNqy_i9GS9-BQUDjPig2WOw,5326
matplotlib/tri/triplot.py,sha256=aZ9O_VVLH0AOne31u11ltLlyVyhqKtyzec7WH3b3pkk,2857
matplotlib/tri/trirefine.py,sha256=DZS_gihMxkUMzuxAijKnEDo4Po_ahIHY7-uGnjUY1Eg,14142
matplotlib/tri/tritools.py,sha256=dC_OcwrFN3gunCe3SgHjQTH_dHBCncM1Fex0bQ_b1Jg,12498
matplotlib/ttconv.cpython-36m-x86_64-linux-gnu.so,sha256=tnAP4SNjDwMwdCZFJjzoDYLYa0wf3LAMZmKd9M_QmS4,83888
matplotlib/type1font.py,sha256=aBak-e5VKpZpH_LyYqNUyAX4vgRisZq4sfeEcV45-j4,12173
matplotlib/units.py,sha256=URBYLJTwt1n-w7A0LcznVlMCMMnLaJFEVrhEHTt0Yv8,6665
matplotlib/widgets.py,sha256=vAYlyIJUze6G7CW9CUmY1LePR5L5XFJrPqvVIYycWzY,94072
mpl_toolkits/axes_grid/__init__.py,sha256=d0j8ET68OmR22G59uzWO4BQ3Jv2kCmJC15nZRAf227M,559
mpl_toolkits/axes_grid/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/anchored_artists.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/angle_helper.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_divider.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_grid.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_rgb.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_size.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axis_artist.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axisline_style.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axislines.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/clip_path.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/colorbar.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/floating_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/grid_finder.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/grid_helper_curvelinear.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/inset_locator.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/parasite_axes.cpython-36.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=Q1NvDXXtKVuX7iUoKFFRw11Wg1eHEGt3-qDWG7DOVxg,269
mpl_toolkits/axes_grid/axes_grid.py,sha256=t2Fc8fM-_qINumuDxctOEYhMI3M1ZfqEVc3th-cnz5g,152
mpl_toolkits/axes_grid/axes_rgb.py,sha256=nKv0IWpKHN2NW5eZqx_rbaZqqMWvYw9GK94gIAVEmE0,301
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=SEWPa2ggZnKkFVX4yaJOPN7KgyV_T-cyjr8UjIjjhPs,272
mpl_toolkits/axes_grid1/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/anchored_artists.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_divider.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_grid.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_rgb.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_size.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/colorbar.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/inset_locator.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/mpl_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/parasite_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid1/anchored_artists.py,sha256=SPXVgw8CLMGTyPScN3Q2WHeWJbhtQo52Fo3DaLJ8yrY,21162
mpl_toolkits/axes_grid1/axes_divider.py,sha256=RNaghesva1N6F4h6J-5Amy15LVqzQyW-FelkfQ_m9n8,29873
mpl_toolkits/axes_grid1/axes_grid.py,sha256=BOh_MSGrTK9_-rAFuY5UST84PRDLObCj2tOg71wYjXI,27243
mpl_toolkits/axes_grid1/axes_rgb.py,sha256=UlErJuhcqtN0skRLckf_v-GvUtAWDTVW401wUzwXOxI,6603
mpl_toolkits/axes_grid1/axes_size.py,sha256=m4LSknVO9c6vcpT1bEZBKYUGkoJ7BOrPnLRTcLmnmFQ,8933
mpl_toolkits/axes_grid1/colorbar.py,sha256=BLNBORudFV18ShwQiiVdceUOTJESQZ4yefVu23yVex0,27428
mpl_toolkits/axes_grid1/inset_locator.py,sha256=EeLwbA6sUhCBFzPVA1MwCTGE8N1APdKZXq4xSiCXG1Y,23722
mpl_toolkits/axes_grid1/mpl_axes.py,sha256=nUXeFjye-NBR1SCXYx1qirV6QIYSB2e01AEeoWlzm7w,4680
mpl_toolkits/axes_grid1/parasite_axes.py,sha256=TjNMInmARhT4tZn2PBafcecuyammO3KBuCnUUYyhbdc,12908
mpl_toolkits/axisartist/__init__.py,sha256=2zsgjqTtP_NXv78MEaKabmfmkjA7yhy77pIcaR57YWs,748
mpl_toolkits/axisartist/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/angle_helper.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axes_divider.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axes_grid.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axes_rgb.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axis_artist.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axisline_style.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axislines.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/clip_path.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/floating_axes.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/grid_finder.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/grid_helper_curvelinear.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/parasite_axes.cpython-36.pyc,,
mpl_toolkits/axisartist/angle_helper.py,sha256=csNOzIHc3s1fvJm7XpuZy74kQY2oeC5przHcVSSFVLc,12932
mpl_toolkits/axisartist/axes_divider.py,sha256=FqYC72nAYkmU9oaawDb7TjMxb1NSjhbYocD1vxwCrvM,509
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=4IdmKz2zRHnRCerIO-lYnf7LiUphSE7fe_Hq5VapDms,44753
mpl_toolkits/axisartist/axisline_style.py,sha256=It8dzmdESmoAmwwEjOs-YjtBydKlNmb41vV6v8tZ1-s,5107
mpl_toolkits/axisartist/axislines.py,sha256=63q7XMODxvM3mwHCmBvtczaOgre-dGigNqavgrDZ844,22082
mpl_toolkits/axisartist/clip_path.py,sha256=_fxHB05pFazHxDmNRXN7xO5EfJaxFPHMwFfqwfAs2uA,3736
mpl_toolkits/axisartist/floating_axes.py,sha256=B3_1_qTFDSWIpfbRxMf9TXyoRzQmwMISdpqU46nC-Uc,16491
mpl_toolkits/axisartist/grid_finder.py,sha256=E_JrpMeAIUj9FZ6Q7_rd4cEYeDbb5TgjoOsJ_5YQIoc,11513
mpl_toolkits/axisartist/grid_helper_curvelinear.py,sha256=yH4--wkTm2C2FSSc_TQcvU-24wJlJLj18JphW5Kzz80,15491
mpl_toolkits/axisartist/parasite_axes.py,sha256=1sQwBEYuXHpaEeObb7cXh0I1xWroYtcvFiEmwrzqK3w,447
mpl_toolkits/mplot3d/__init__.py,sha256=V2iPIP9VyRhoJsFWnQf5AkfyI1GSSP9H6hICEe9edJo,27
mpl_toolkits/mplot3d/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/art3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/axes3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/axis3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/proj3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/art3d.py,sha256=Juj1shJAObe_JDhF3Fm4qtMVPmvLMUYt2lKp8dakteQ,24879
mpl_toolkits/mplot3d/axes3d.py,sha256=EDZc-nuqbI6HNt7oioFRL36EKODH8HTA68EsElSW_II,103257
mpl_toolkits/mplot3d/axis3d.py,sha256=z8Q20DsMzFvA-jYWFsbbo-fFB7Yzegdlg7Wz1Ws5VPE,18574
mpl_toolkits/mplot3d/proj3d.py,sha256=JNb8VcfoAOwRJMLAOT6pdX2PDE7fCx5L48PGdIACzvU,4612
mpl_toolkits/tests/__init__.py,sha256=iPdasxJf0vpIi11tQ98OVSQgS0UaPUyOEGGfAryAhIA,381
mpl_toolkits/tests/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/conftest.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axes_grid.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axes_grid1.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_angle_helper.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_axis_artist.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_axislines.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_clip_path.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_floating_axes.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_grid_finder.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_grid_helper_curvelinear.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_mplot3d.cpython-36.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=EStruex2GqxBIGm49SXqrn8lO4-_XhsAnvs5CmtUawc,3872
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.pdf,sha256=zkfSOR2bJYC_X425qnXHMmGJPSlLSpFs53YB_R760Gw,6603
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png,sha256=SoyN30SsuvEECZyB_ReGP3ZKGZJazOp05dXa3YUn7Jc,47796
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.svg,sha256=Kb_zdIzZG6JKnztMcmOUG4esPsuteljB_A2sxrhIA3Y,18046
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.pdf,sha256=YI5gzs8NK6fWo6JB0wf8xeZ-FrHlS3P8DSCccsLU4fE,7197
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png,sha256=Qw909B4nDmV9DlMuo1DKk7y5ndjtvni5d_DcysmG9VA,100466
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.svg,sha256=iU1Pk60SDC89km6bwz9Li9mXdNdZ_Vn15bkbAUG2iKo,30591
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png,sha256=p9nV0cr7ZqhmM5VRHYcByR_QWKh91b8jjt71nkrq3Bc,66289
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png,sha256=9RXKAlcPSrQYmhvqH9JMnlLaR62satjwyUq1joXcy6g,64595
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.pdf,sha256=CtzH5MJNMY_hZGAyp9py9PLI0a8kJ-jNnpQKQYtoQEE,25170
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png,sha256=tii1IakS8MC_Vuwd95HhcyM0iq4zGN5DxgRFfB9mKu8,83161
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.svg,sha256=3VjmHaN5hRXJ-HH9duS1M6nR8gkgBOCtb3zruWcSujU,67618
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.pdf,sha256=H7FyZjuiA12CzP8FDDnVNf42HxGfMlg_8rA57HA1sIo,52812
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png,sha256=Jb-fhAcgogE8jn9DSsaqInUfWC7D_5Pf3QRf7XWAX2Q,42575
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.svg,sha256=cz9TicdIGJ_8UdHTKQZ76n2rAv6Rx2EELPg8AyAEgMU,173077
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.pdf,sha256=raTRNOdfYPFJvZiM7BbRbdWRPElQe0sn6jsKH63TTyA,3950
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png,sha256=dE8eHoj43eePB44F1nLM2RLj8iqw8rCYI3D0VD3gUg0,39694
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.svg,sha256=BM-PcmZJ-8iyB4wUWQxcMuskmDXemrsX52LKPYEz850,11093
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.pdf,sha256=6pKrI8lUIPxRwi3ofm8DK8UqNeZsBYprXwX51vwFv1Y,4354
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png,sha256=DQT-NruHCeG5LKpjG-dlLln3aCoPKhua5PQnHTafBGU,60217
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.svg,sha256=c32m_X6tFYlY5PCVYJ0cPzvZDTbgRA-GIwzDUVTNKYA,12919
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf,sha256=Y-C6q8kG4QdVsBpck1Qg90W87rSnbOzrDQP1v7qFZDI,53962
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png,sha256=rGHpqTyXqt1PvCSvSrP7Dnd0uNeeEmPPgJwADxXdfM8,39763
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.svg,sha256=TQ9KQlKC0BU4lnayB2S8ArbsfAh87qKjFEh5WKjWbvk,286241
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_closed.pdf,sha256=mJYYIj01eM9ouCPpoeWu-KDrXe3_o2um4_JddGAzWGg,2680
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png,sha256=ePzSA-iFaQbmH603vw1jhs9vyIt45xXnbpIuUF3a1l8,52065
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.svg,sha256=K-D9vp-jB8KFTilVotfIQvuhG3qTMT04XZ6LUIfpZ60,8594
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.pdf,sha256=T4nKDMCEi2zKpoq5FOsdyMQJd0igS9yBeyUL84a0oso,18589
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png,sha256=PBllNI1kHf1rz-oPK507OwsPNE9DPwivXAVJM9DemBI,104755
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.svg,sha256=_cwXN4aH-mQx5ADarZtXsY-vaeRFg4dPuVmP6S9NZbM,128623
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.pdf,sha256=DSFSucOBU22R3zmG8eKlWWtLy5Wb7L7wqZ1B7CMybKc,7405
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.png,sha256=98D3k5QIL7KugUwzqJhdLtp9dgDGgx8hGa9_u8cvX6o,37954
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.svg,sha256=H_b_HtjccyLTNDjIqUek6DwQugfsyb88nVBepBzFdTo,7745
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.pdf,sha256=_VIBNh32vX_K9QrH-0o4z13FAE0JZQuOzOfkCTsSe7c,10939
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png,sha256=67yp7-6f-vDiYTmCqMFfuIEGly5UHCCUOV84YJtLsX8,80392
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.svg,sha256=JO8n2RUeQANeCpq_PmtyjmVPqMmuqvZQg7vk_3hsv8I,68796
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.pdf,sha256=93umAGrsz8bYekxMETAlU67eCTY3tzycxpVmm2M7eEE,5847
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png,sha256=MDaocusHz6Itinjm2j6fnDh-rl1fqVjnqM89nP8bwZs,43155
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.svg,sha256=yc-PIx36-DdoCE1Vd8JRih45GoBR7eVtdi6wMZVZUXU,12494
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png,sha256=Y7De9BIFLp0Ova4fk9IcXloNjiwmifTrFA1IfVJA3aE,41598
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf,sha256=OSvMZiGJzmVdtv_e0XHIBw4bLSY7DLtWGDAYgh6vNuo,48096
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png,sha256=Ok0UmO2DELze2yK8mRx0CifmRAgvjyS1IvERsBRvFlU,54712
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.svg,sha256=CaqTIT-jdKSKfCqp8GC_QAc_3Dxwgr97uvDR-a4xdcw,270325
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png,sha256=tW1q8o6BAlqGD-ILqAXXTBxEt08fwIwEnK-L7n4fHFo,43405
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.pdf,sha256=CfWlocbvwHK48CADjhNjjXJ2eHsSvoNWWpz33RvkuQQ,13906
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png,sha256=cuahU107LG85pT3kyHngOPV2GchTUwu1AkLYbb4UDd8,77741
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.svg,sha256=MmcVy3u0Syymf933rIsaUY8XOv6b9r6ScVXx33NFxBg,36357
mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png,sha256=8IjYmJP6cBhnPGLz-WDyn7UUMYZ10Kz2MpjOFwDUVow,71328
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.pdf,sha256=OVbK3ausYMApR7_UJPx0pf3bt4A3CpZumhAVhzQMbs0,169155
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png,sha256=nO0gJBIluLEX3mlxXY3C6bx-9Jf_xJyXAnTXKnqrIkQ,99103
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.svg,sha256=PFUTTF34KiJcol6J5JmRjkaillzRWAgsbfxgEhpWxmA,103333
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png,sha256=45y5oF9MzbR07cP-T9CNF7yLWo8qUDoG3E91kc6jueE,94492
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png,sha256=4BUCO65oRVuxgOs3i06OFVNWi_UJLelUQzJ7WCzhdOQ,179128
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png,sha256=DMDM2KV84Z0TH7BlWJAuESle0NbFcCsmDF9Q0rPmP3w,65236
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png,sha256=ze6vMiuPmRj7FOX_1ltXMWwQNaRD5jl3drzXbdMKcFs,96319
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png,sha256=6X8fQ-dYqDlRt9oSoeYbic2mQb1DDlqPfAUpMbh2O9U,136235
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png,sha256=mG67qqJF-9TKYbacsbVknSnUOj8D9m0sYmy0imfwY2M,60940
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png,sha256=OeInzIzGNXG490czp9McRMqEMcgiz0ABkUky7QlTwfE,122177
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.pdf,sha256=3gYea_CtpNg1UmurOhyWt3vHZSkLX1-trEY779yrxbc,36169
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png,sha256=epmsR4rWGzh7prW3RL_t8ZcUEsphM5bc0t5j__iauOY,108371
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.svg,sha256=-hjWYF-WSE3aDuCSpHYztjeV_yktEqatDK2yZyn_rhE,85892
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=UCQFk5p-9sbTMCS6RKk7BfyCiTb9yRnsarH23eUem0o,1638
mpl_toolkits/tests/test_axes_grid1.py,sha256=mHdjXAd-3X1OEW0yS9iap1i2GUuvqtH4-UnnOICT_U4,15028
mpl_toolkits/tests/test_axisartist_angle_helper.py,sha256=2jLmTrH4fw3Xty2CwaBsRJISb8qxWWn8ALRa4HL2FQA,5702
mpl_toolkits/tests/test_axisartist_axis_artist.py,sha256=h8UXVxnt-fsfvjEOLxnyrwA4z0b7Lf-1UtvIh_SNaI4,2893
mpl_toolkits/tests/test_axisartist_axislines.py,sha256=nzxykaFzR1XaQ7KlJNf9VNDXoXRQY4BZKuH_iIyiCHk,2266
mpl_toolkits/tests/test_axisartist_clip_path.py,sha256=7K1Y-2DPbDdyvpAHq3XEDaXfsikw-u8v7olwaqwZ53o,1054
mpl_toolkits/tests/test_axisartist_floating_axes.py,sha256=bTaH-fTMJum4DAjH5h4t-hH3W5BNNw9LuiWPaC24j6Q,4165
mpl_toolkits/tests/test_axisartist_grid_finder.py,sha256=e65sLudWFIXeU08Sis3_SI1JEI6eq8YqKj-80F_Nohk,325
mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py,sha256=TpV3ShQOPAk57KV-97dWfpULRws0zyAOQwn3JpejTe4,7487
mpl_toolkits/tests/test_mplot3d.py,sha256=BUvMUV5OkJskzLZ1KBIIFgKIWoiWaw38FOYBJT0_xRM,26306
pylab.py,sha256=u_By3CHla-rBMg57egFXIxZ3P_J6zEkSu_dNpBcH5pw,90
@@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.31.1)
Root-Is-Purelib: false
Tag: cp36-cp36m-manylinux1_x86_64
@@ -1,2 +0,0 @@
mpl_toolkits
mpl_toolkits
@@ -1,3 +0,0 @@
matplotlib
mpl_toolkits
pylab
File diff suppressed because it is too large Load Diff
@@ -1,210 +0,0 @@
# 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">
/* 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];
}
document.getElementById(this.slider_id).max = this.frames.length - 1;
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>
"""
# HTML template for HTMLWriter
DISPLAY_TEMPLATE = """
<div class="animation" align="center">
<img id="_anim_img{id}">
<br>
<input id="_anim_slider{id}" type="range" style="width:350px"
name="points" min="0" max="1" step="1" value="0"
onchange="anim{id}.set_frame(parseInt(this.value));"></input>
<br>
<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>
<form action="#n" name="_anim_loop_select{id}" class="anim_control">
<input type="radio" name="state"
value="once" {once_checked}> Once </input>
<input type="radio" name="state"
value="loop" {loop_checked}> Loop </input>
<input type="radio" name="state"
value="reflect" {reflect_checked}> Reflect </input>
</form>
</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}";
}}
"""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,729 +0,0 @@
"""
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 encomapss the legend, and that is
# how legends get included (axes legeneds, not figure legends)
# - colorbars are sibblings 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 implimented
#
# Todo: AnchoredOffsetbox connected to gridspecs or axes. This would
# be more general way to add extra-axes annotations.
import numpy as np
import logging
import warnings
from matplotlib.legend import Legend
import matplotlib.transforms as transforms
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):
"""
helper function to make sure all axes in the
figure have a finite width and height. If not, return False
"""
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 neq colnumC, the two subplotspecs are stacked next to
each other, with the appropriate order.
b) if colnum0 == columnC 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:
warnings.warn('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 algrithm 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.
if fig._suptitle is not None and fig._suptitle._layoutbox is not None:
sup = fig._suptitle
bbox = invTransFig(sup.get_window_extent(renderer=renderer))
height = bbox.y1 - bbox.y0
sup._layoutbox.edit_height(height+h_pad)
# 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 conatain 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')
else:
warnings.warn('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 shoudn't have too much ill effect. The worst is that
someone querrying 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()
if ss0.num2 is None:
ss0.num2 = ss0.num1
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_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
ax.set_facecolor((1, 0, 0, 0))
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)
bbox = invTransFig(tightbbox)
# 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))
# 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()
if ss0.num2 is None:
ss0.num2 = ss0.num1
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]):
ss0 = ax.get_subplotspec()
# 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):
"""
arrange the subplotspec children of this gridspec, and then recursively
do the same of any gridspec children of those gridspecs...
"""
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()
if ss0.num2 is None:
ss0.num2 = ss0.num1
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)
if ssc.num2 is None:
ssc.num2 = ssc.num1
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 oeverly 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 oeverly 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
@@ -1,735 +0,0 @@
"""
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
import warnings
import matplotlib
_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(object):
"""
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'))
# margis 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 fogure 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
otehr 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 = float(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.add.accumulate(np.ravel(
list(zip(sepHeights, cellHeights))))
# 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 = float(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.add.accumulate(np.ravel(list(zip(sepWidths, cellWidths))))
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)]
rowNum, colNum = divmod(subspec.num1, ncols)
figBottom = figBottoms[rowNum]
figTop = figTops[rowNum]
figLeft = figLefts[colNum]
figRight = figRights[colNum]
if subspec.num2 is not None:
rowNum2, colNum2 = divmod(subspec.num2, ncols)
figBottom2 = figBottoms[rowNum2]
figTop2 = figTops[rowNum2]
figLeft2 = figLefts[colNum2]
figRight2 = figRights[colNum2]
figBottom = min(figBottom, figBottom2)
figLeft = min(figLeft, figLeft2)
figTop = max(figTop, figTop2)
figRight = max(figRight, figRight2)
# 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...
'''
global _layoutboxobjnum
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... This signals 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:
rect = child.get_rect()
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)
File diff suppressed because it is too large Load Diff
@@ -1,134 +0,0 @@
"""
Manage figures for pyplot interface.
"""
import atexit
import gc
import sys
class Gcf(object):
"""
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 = []
for m in oldQue:
if m != manager:
cls._activeQue.append(m)
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)
@@ -1,21 +0,0 @@
# 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": "8858a0d1bdd149a0897789e8503ac586be14676d",
"version": "3.0.2"
}
''' # END VERSION_JSON
def get_versions():
return json.loads(version_json)
@@ -1,562 +0,0 @@
"""
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:
>>> from matplotlib import rcParams
>>> import os.path
>>> afm_fname = os.path.join(rcParams['datapath'],
... 'fonts', 'afm', 'ptmr8a.afm')
>>>
>>> from matplotlib.afm import AFM
>>> with open(afm_fname, '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 re
import sys
from ._mathtext_data import uni2type1
from matplotlib.cbook import deprecated
# 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 mpl from crashing on
# these JDH (2009-11-06)
def _to_int(x):
return int(float(x))
_to_float = float
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 _sanity_check(fh):
"""
Check if the file at least looks like AFM.
If not, raise :exc:`RuntimeError`.
"""
# Remember the file position in case the caller wants to
# do something else with the file.
pos = fh.tell()
try:
line = next(fh)
finally:
fh.seek(pos, 0)
# 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 line.
if not line.startswith(b'StartFontMetrics'):
raise RuntimeError('Not an AFM file')
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
"""
headerConverters = {
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_int,
b'UnderlineThickness': _to_int,
b'Version': _to_str,
b'Notice': _to_str,
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 = {}
for line in fh:
line = line.rstrip()
if line.startswith(b'Comment'):
continue
lst = line.split(b' ', 1)
key = lst[0]
if len(lst) == 2:
val = lst[1]
else:
val = b''
try:
d[key] = headerConverters[key](val)
except ValueError:
print('Value error parsing header in AFM:', key, val,
file=sys.stderr)
continue
except KeyError:
print('Found an unknown keyword in AFM header (was %r)' % key,
file=sys.stderr)
continue
if key == b'StartCharMetrics':
return d
raise RuntimeError('Bad parse')
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
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']
@deprecated("3.0", "Use the class AFM instead.")
def parse_afm(fh):
return _parse_afm(fh)
def _parse_afm(fh):
"""
Parse the Adobe Font Metrics file in file handle *fh*.
Returns
-------
header : dict
A header dict. See :func:`_parse_header`.
cmetrics_by_ascii : dict
From :func:`_parse_char_metrics`.
cmetrics_by_name : dict
From :func:`_parse_char_metrics`.
kernpairs : dict
From :func:`_parse_kern_pairs`.
composites : dict
From :func:`_parse_composites`
"""
_sanity_check(fh)
header = _parse_header(fh)
cmetrics_by_ascii, cmetrics_by_name = _parse_char_metrics(fh)
kernpairs, composites = _parse_optional(fh)
return header, cmetrics_by_ascii, cmetrics_by_name, kernpairs, composites
class AFM(object):
def __init__(self, fh):
"""Parse the AFM file in file object *fh*."""
(self._header,
self._metrics,
self._metrics_by_name,
self._kern,
self._composite) = _parse_afm(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)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,2 +0,0 @@
from ._subplots import *
from ._axes import *
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,242 +0,0 @@
import functools
import warnings
from matplotlib import 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(object):
"""
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):
"""
*fig* is a :class:`matplotlib.figure.Figure` instance.
*args* is the tuple (*numRows*, *numCols*, *plotNum*), where
the array of subplots in the figure has dimensions *numRows*,
*numCols*, and where *plotNum* is the number of the subplot
being created. *plotNum* starts at 1 in the upper left
corner and increases to the right.
If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the
decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*.
"""
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 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(
("num must be 1 <= num <= {maxn}, not {num}"
).format(maxn=rows*cols, num=num))
self._subplotspec = GridSpec(
rows, cols, figure=self.figure)[int(num) - 1]
# num - 1 for converting from MATLAB to python indexing
else:
raise ValueError('Illegal argument(s) to subplot: %s' % (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.rowNum, self.colNum, self.numRows, self.numCols = \
self.get_subplotspec().get_position(self.figure,
return_all=True)
def is_first_col(self):
return self.colNum == 0
def is_first_row(self):
return self.rowNum == 0
def is_last_row(self):
return self.rowNum == self.numRows - 1
def is_last_col(self):
return self.colNum == self.numCols - 1
# COVERAGE NOTE: Never used internally.
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, *kl, **kwargs):
"""
Make a twinx axes of self. This is used for twinx and twiny.
"""
from matplotlib.projections import process_projection_requirements
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.")
kl = (self.get_subplotspec(),) + kl
projection_class, kwargs, key = process_projection_requirements(
self.figure, *kl, **kwargs)
ax2 = subplot_class_factory(projection_class)(self.figure,
*kl, **kwargs)
self.figure.add_subplot(ax2)
self.set_adjustable('datalim')
ax2.set_adjustable('datalim')
if self._layoutbox is not None and ax2._layoutbox is not None:
# make the layout boxes be explicitly the same
ax2._layoutbox.constrain_same(self._layoutbox)
ax2._poslayoutbox.constrain_same(self._poslayoutbox)
self._twinned_axes.join(self, ax2)
return ax2
# 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))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,433 +0,0 @@
"""
`ToolManager`
Class that makes the bridge between user interaction (key press,
toolbar clicks, ..) and the actions in response to the user inputs.
"""
import warnings
import matplotlib.cbook as cbook
import matplotlib.widgets as widgets
from matplotlib.rcsetup import validate_stringlist
import matplotlib.backend_tools as tools
class ToolEvent(object):
"""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(object):
"""
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(object):
"""
Helper class that groups all the user interactions for 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):
warnings.warn('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',
on_press)
#...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 : string
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 : string
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:
warnings.warn('Key %s changed from %s to %s' %
(k, self._keys[k], name))
self._keys[k] = name
def remove_tool(self, name):
"""
Remove tool from `ToolManager`
Parameters
----------
name : string
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, this 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:
warnings.warn('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 : string
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):
"""
Trigger on a tool
Method to actually trigger the 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):
"""Return the tools controlled by `ToolManager`"""
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:
warnings.warn("ToolManager does not control tool %s" % name)
return None
return self._tools[name]
File diff suppressed because it is too large Load Diff
@@ -1,107 +0,0 @@
import importlib
import logging
import os
import sys
import traceback
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")
or sys.modules.get("pgi.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
@cbook.deprecated("3.0")
def pylab_setup(name=None):
"""
Return new_figure_manager, draw_if_interactive and show for pyplot.
This provides the backend-specific functions that are used by pyplot to
abstract away the difference between backends.
Parameters
----------
name : str, optional
The name of the backend to use. If `None`, falls back to
``matplotlib.get_backend()`` (which return :rc:`backend`).
Returns
-------
backend_mod : module
The module which contains the backend of choice
new_figure_manager : function
Create a new figure manager (roughly maps to GUI window)
draw_if_interactive : function
Redraw the current figure if pyplot is interactive
show : function
Show (and possibly block) any unshown figures.
"""
# Import the requested backend into a generic module object.
if name is None:
name = matplotlib.get_backend()
backend_name = (name[9:] if name.startswith("module://")
else "matplotlib.backends.backend_{}".format(name.lower()))
backend_mod = importlib.import_module(backend_name)
# Create a local Backend class whose body corresponds to the contents of
# the backend module. This allows the Backend class to fill in the missing
# methods through inheritance.
Backend = type("Backend", (_Backend,), vars(backend_mod))
# Need to keep a global reference to the backend for compatibility reasons.
# See https://github.com/matplotlib/matplotlib/issues/6092
global backend
backend = name
_log.debug('backend %s version %s', name, Backend.backend_version)
return (backend_mod,
Backend.new_figure_manager,
Backend.draw_if_interactive,
Backend.show)
File diff suppressed because it is too large Load Diff
@@ -1,47 +0,0 @@
"""
GObject compatibility loader; supports ``gi`` and ``pgi``.
The binding selection rules are as follows:
- if ``gi`` has already been imported, use it; else
- if ``pgi`` has already been imported, use it; else
- if ``gi`` can be imported, use it; else
- if ``pgi`` can be imported, use it; else
- error out.
Thus, to force usage of PGI when both bindings are installed, import it first.
"""
import importlib
import sys
if "gi" in sys.modules:
import gi
elif "pgi" in sys.modules:
import pgi as gi
else:
try:
import gi
except ImportError:
try:
import pgi as gi
except ImportError:
raise ImportError("The GTK3 backends require PyGObject or pgi")
from .backend_cairo import cairo # noqa
# The following combinations are allowed:
# gi + pycairo
# gi + cairocffi
# pgi + cairocffi
# (pgi doesn't work with pycairo)
# We always try to import cairocffi first so if a check below fails it means
# that cairocffi was unavailable to start with.
if gi.__name__ == "pgi" and cairo.__name__ == "cairo":
raise ImportError("pgi and pycairo are not compatible")
if gi.__name__ == "pgi" and gi.version_info < (0, 0, 11, 2):
raise ImportError("The GTK3 backends are incompatible with pgi<0.0.11.2")
gi.require_version("Gtk", "3.0")
globals().update(
{name:
importlib.import_module("{}.repository.{}".format(gi.__name__, name))
for name in ["GLib", "GObject", "Gtk", "Gdk"]})
@@ -1,595 +0,0 @@
"""
An agg http://antigrain.com/ backend
Features that are implemented
* capstyles and join styles
* dashes
* linewidth
* lines, rectangles, ellipses
* clipping to a rectangle
* output to RGBA and PNG, optionally JPEG and TIFF
* alpha blending
* DPI scaling properly - everything scales properly (dashes, linewidths, etc)
* draw polygon
* freetype2 w/ ft2font
TODO:
* integrate screen dpi w/ ppi and text
"""
try:
import threading
except ImportError:
import dummy_threading as threading
import numpy as np
from collections import OrderedDict
from math import radians, cos, sin
from matplotlib import cbook, rcParams, __version__
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
from matplotlib.font_manager import findfont, get_font
from matplotlib.ft2font import (LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING,
LOAD_DEFAULT, LOAD_NO_AUTOHINT)
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
from matplotlib.transforms import Bbox, BboxBase
from matplotlib import colors as mcolors
from matplotlib.backends._backend_agg import RendererAgg as _RendererAgg
from matplotlib import _png
from matplotlib.backend_bases import _has_pil
if _has_pil:
from PIL import Image
backend_version = 'v2.2'
def get_hinting_flag():
mapping = {
True: LOAD_FORCE_AUTOHINT,
False: LOAD_NO_HINTING,
'either': LOAD_DEFAULT,
'native': LOAD_NO_AUTOHINT,
'auto': LOAD_FORCE_AUTOHINT,
'none': LOAD_NO_HINTING
}
return mapping[rcParams['text.hinting']]
class RendererAgg(RendererBase):
"""
The renderer handles all the drawing primitives using a graphics
context instance that controls the colors/styles
"""
# we want to cache the fonts at the class level so that when
# multiple figures are created we can reuse them. This helps with
# a bug on windows where the creation of too many figures leads to
# too many open file handles. However, storing them at the class
# level is not thread safe. The solution here is to let the
# FigureCanvas acquire a lock on the fontd at the start of the
# draw, and release it when it is done. This allows multiple
# renderers to share the cached fonts, but only one figure can
# draw at time and so the font cache is used by only one
# renderer at a time.
lock = threading.RLock()
def __init__(self, width, height, dpi):
RendererBase.__init__(self)
self.dpi = dpi
self.width = width
self.height = height
self._renderer = _RendererAgg(int(width), int(height), dpi)
self._filter_renderers = []
self._update_methods()
self.mathtext_parser = MathTextParser('Agg')
self.bbox = Bbox.from_bounds(0, 0, self.width, self.height)
def __getstate__(self):
# We only want to preserve the init keywords of the Renderer.
# Anything else can be re-created.
return {'width': self.width, 'height': self.height, 'dpi': self.dpi}
def __setstate__(self, state):
self.__init__(state['width'], state['height'], state['dpi'])
def _update_methods(self):
self.draw_gouraud_triangle = self._renderer.draw_gouraud_triangle
self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles
self.draw_image = self._renderer.draw_image
self.draw_markers = self._renderer.draw_markers
self.draw_path_collection = self._renderer.draw_path_collection
self.draw_quad_mesh = self._renderer.draw_quad_mesh
self.copy_from_bbox = self._renderer.copy_from_bbox
self.get_content_extents = self._renderer.get_content_extents
def tostring_rgba_minimized(self):
extents = self.get_content_extents()
bbox = [[extents[0], self.height - (extents[1] + extents[3])],
[extents[0] + extents[2], self.height - extents[1]]]
region = self.copy_from_bbox(bbox)
return np.array(region), extents
def draw_path(self, gc, path, transform, rgbFace=None):
"""
Draw the path
"""
nmax = rcParams['agg.path.chunksize'] # here at least for testing
npts = path.vertices.shape[0]
if (nmax > 100 and npts > nmax and path.should_simplify and
rgbFace is None and gc.get_hatch() is None):
nch = np.ceil(npts / nmax)
chsize = int(np.ceil(npts / nch))
i0 = np.arange(0, npts, chsize)
i1 = np.zeros_like(i0)
i1[:-1] = i0[1:] - 1
i1[-1] = npts
for ii0, ii1 in zip(i0, i1):
v = path.vertices[ii0:ii1, :]
c = path.codes
if c is not None:
c = c[ii0:ii1]
c[0] = Path.MOVETO # move to end of last chunk
p = Path(v, c)
try:
self._renderer.draw_path(gc, p, transform, rgbFace)
except OverflowError:
raise OverflowError("Exceeded cell block limit (set "
"'agg.path.chunksize' rcparam)")
else:
try:
self._renderer.draw_path(gc, path, transform, rgbFace)
except OverflowError:
raise OverflowError("Exceeded cell block limit (set "
"'agg.path.chunksize' rcparam)")
def draw_mathtext(self, gc, x, y, s, prop, angle):
"""
Draw the math text using matplotlib.mathtext
"""
ox, oy, width, height, descent, font_image, used_characters = \
self.mathtext_parser.parse(s, self.dpi, prop)
xd = descent * sin(radians(angle))
yd = descent * cos(radians(angle))
x = np.round(x + ox + xd)
y = np.round(y - oy + yd)
self._renderer.draw_text_image(font_image, x, y + 1, angle, gc)
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
"""
Render the text
"""
if ismath:
return self.draw_mathtext(gc, x, y, s, prop, angle)
flags = get_hinting_flag()
font = self._get_agg_font(prop)
if font is None:
return None
if len(s) == 1 and ord(s) > 127:
font.load_char(ord(s), flags=flags)
else:
# We pass '0' for angle here, since it will be rotated (in raster
# space) in the following call to draw_text_image).
font.set_text(s, 0, flags=flags)
font.draw_glyphs_to_bitmap(antialiased=rcParams['text.antialiased'])
d = font.get_descent() / 64.0
# The descent needs to be adjusted for the angle.
xo, yo = font.get_bitmap_offset()
xo /= 64.0
yo /= 64.0
xd = -d * sin(radians(angle))
yd = d * cos(radians(angle))
self._renderer.draw_text_image(
font, np.round(x - xd + xo), np.round(y + yd + yo) + 1, angle, gc)
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 in ["TeX", "TeX!"]:
# todo: handle props
size = prop.get_size_in_points()
texmanager = self.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
if ismath:
ox, oy, width, height, descent, fonts, used_characters = \
self.mathtext_parser.parse(s, self.dpi, prop)
return width, height, descent
flags = get_hinting_flag()
font = self._get_agg_font(prop)
font.set_text(s, 0.0, flags=flags)
w, h = font.get_width_height() # width and height of unrotated string
d = font.get_descent()
w /= 64.0 # convert from subpixels
h /= 64.0
d /= 64.0
return w, h, d
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
# todo, handle props, angle, origins
size = prop.get_size_in_points()
texmanager = self.get_texmanager()
Z = texmanager.get_grey(s, size, self.dpi)
Z = np.array(Z * 255.0, np.uint8)
w, h, d = self.get_text_width_height_descent(s, prop, ismath)
xd = d * sin(radians(angle))
yd = d * cos(radians(angle))
x = np.round(x + xd)
y = np.round(y + yd)
self._renderer.draw_text_image(Z, x, y, angle, gc)
def get_canvas_width_height(self):
'return the canvas width and height in display coords'
return self.width, self.height
def _get_agg_font(self, prop):
"""
Get the font for text instance t, caching for efficiency
"""
fname = findfont(prop)
font = get_font(fname)
font.clear()
size = prop.get_size_in_points()
font.set_size(size, self.dpi)
return font
def points_to_pixels(self, points):
"""
convert point measures to pixes using dpi and the pixels per
inch of the display
"""
return points * self.dpi / 72
def tostring_rgb(self):
return self._renderer.tostring_rgb()
def tostring_argb(self):
return self._renderer.tostring_argb()
def buffer_rgba(self):
return self._renderer.buffer_rgba()
def clear(self):
self._renderer.clear()
def option_image_nocomposite(self):
# It is generally faster to composite each image directly to
# the Figure, and there's no file size benefit to compositing
# with the Agg backend
return True
def option_scale_image(self):
"""
agg backend doesn't support arbitrary scaling of image.
"""
return False
def restore_region(self, region, bbox=None, xy=None):
"""
Restore the saved region. If bbox (instance of BboxBase, or
its extents) is given, only the region specified by the bbox
will be restored. *xy* (a tuple of two floasts) optionally
specifies the new position (the LLC of the original region,
not the LLC of the bbox) where the region will be restored.
>>> region = renderer.copy_from_bbox()
>>> x1, y1, x2, y2 = region.get_extents()
>>> renderer.restore_region(region, bbox=(x1+dx, y1, x2, y2),
... xy=(x1-dx, y1))
"""
if bbox is not None or xy is not None:
if bbox is None:
x1, y1, x2, y2 = region.get_extents()
elif isinstance(bbox, BboxBase):
x1, y1, x2, y2 = bbox.extents
else:
x1, y1, x2, y2 = bbox
if xy is None:
ox, oy = x1, y1
else:
ox, oy = xy
# The incoming data is float, but the _renderer type-checking wants
# to see integers.
self._renderer.restore_region(region, int(x1), int(y1),
int(x2), int(y2), int(ox), int(oy))
else:
self._renderer.restore_region(region)
def start_filter(self):
"""
Start filtering. It simply create a new canvas (the old one is saved).
"""
self._filter_renderers.append(self._renderer)
self._renderer = _RendererAgg(int(self.width), int(self.height),
self.dpi)
self._update_methods()
def stop_filter(self, post_processing):
"""
Save the plot in the current canvas as a image and apply
the *post_processing* function.
def post_processing(image, dpi):
# ny, nx, depth = image.shape
# image (numpy array) has RGBA channels and has a depth of 4.
...
# create a new_image (numpy array of 4 channels, size can be
# different). The resulting image may have offsets from
# lower-left corner of the original image
return new_image, offset_x, offset_y
The saved renderer is restored and the returned image from
post_processing is plotted (using draw_image) on it.
"""
width, height = int(self.width), int(self.height)
buffer, (l, b, w, h) = self.tostring_rgba_minimized()
self._renderer = self._filter_renderers.pop()
self._update_methods()
if w > 0 and h > 0:
img = np.fromstring(buffer, np.uint8)
img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255.,
self.dpi)
gc = self.new_gc()
if img.dtype.kind == 'f':
img = np.asarray(img * 255., np.uint8)
img = img[::-1]
self._renderer.draw_image(gc, l + ox, height - b - h + oy, img)
class FigureCanvasAgg(FigureCanvasBase):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...
Attributes
----------
figure : `matplotlib.figure.Figure`
A high-level Figure instance
"""
def copy_from_bbox(self, bbox):
renderer = self.get_renderer()
return renderer.copy_from_bbox(bbox)
def restore_region(self, region, bbox=None, xy=None):
renderer = self.get_renderer()
return renderer.restore_region(region, bbox, xy)
def draw(self):
"""
Draw the figure using the renderer.
"""
self.renderer = self.get_renderer(cleared=True)
# acquire a lock on the shared font cache
RendererAgg.lock.acquire()
toolbar = self.toolbar
try:
self.figure.draw(self.renderer)
# A GUI class may be need to update a window using this draw, so
# don't forget to call the superclass.
super().draw()
finally:
RendererAgg.lock.release()
def get_renderer(self, cleared=False):
l, b, w, h = self.figure.bbox.bounds
key = w, h, self.figure.dpi
try: self._lastKey, self.renderer
except AttributeError: need_new_renderer = True
else: need_new_renderer = (self._lastKey != key)
if need_new_renderer:
self.renderer = RendererAgg(w, h, self.figure.dpi)
self._lastKey = key
elif cleared:
self.renderer.clear()
return self.renderer
def tostring_rgb(self):
'''Get the image as an RGB byte string.
`draw` must be called at least once before this function will work and
to update the renderer for any subsequent changes to the Figure.
Returns
-------
bytes
'''
return self.renderer.tostring_rgb()
def tostring_argb(self):
'''Get the image as an ARGB byte string
`draw` must be called at least once before this function will work and
to update the renderer for any subsequent changes to the Figure.
Returns
-------
bytes
'''
return self.renderer.tostring_argb()
def buffer_rgba(self):
'''Get the image as an RGBA byte string.
`draw` must be called at least once before this function will work and
to update the renderer for any subsequent changes to the Figure.
Returns
-------
bytes
'''
return self.renderer.buffer_rgba()
def print_raw(self, filename_or_obj, *args, **kwargs):
FigureCanvasAgg.draw(self)
renderer = self.get_renderer()
with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \
cbook.open_file_cm(filename_or_obj, "wb") as fh:
fh.write(renderer._renderer.buffer_rgba())
print_rgba = print_raw
def print_png(self, filename_or_obj, *args, **kwargs):
"""
Write the figure to a PNG file.
Parameters
----------
filename_or_obj : str or PathLike or file-like object
The file to write to.
metadata : dict, optional
Metadata in the PNG file as key-value pairs of bytes or latin-1
encodable strings.
According to the PNG specification, keys must be shorter than 79
chars.
The `PNG specification`_ defines some common keywords that may be
used as appropriate:
- Title: Short (one line) title or caption for image.
- Author: Name of image's creator.
- Description: Description of image (possibly long).
- Copyright: Copyright notice.
- Creation Time: Time of original image creation
(usually RFC 1123 format).
- Software: Software used to create the image.
- Disclaimer: Legal disclaimer.
- Warning: Warning of nature of content.
- Source: Device used to create the image.
- Comment: Miscellaneous comment;
conversion from other image format.
Other keywords may be invented for other purposes.
If 'Software' is not given, an autogenerated value for matplotlib
will be used.
For more details see the `PNG specification`_.
.. _PNG specification: \
https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords
"""
FigureCanvasAgg.draw(self)
renderer = self.get_renderer()
version_str = (
'matplotlib version ' + __version__ + ', http://matplotlib.org/')
metadata = OrderedDict({'Software': version_str})
user_metadata = kwargs.pop("metadata", None)
if user_metadata is not None:
metadata.update(user_metadata)
with cbook._setattr_cm(renderer, dpi=self.figure.dpi), \
cbook.open_file_cm(filename_or_obj, "wb") as fh:
_png.write_png(renderer._renderer, fh,
self.figure.dpi, metadata=metadata)
def print_to_buffer(self):
FigureCanvasAgg.draw(self)
renderer = self.get_renderer()
with cbook._setattr_cm(renderer, dpi=self.figure.dpi):
return (renderer._renderer.buffer_rgba(),
(int(renderer.width), int(renderer.height)))
if _has_pil:
# add JPEG support
def print_jpg(self, filename_or_obj, *args, dryrun=False, **kwargs):
"""
Write the figure to a JPEG file.
Parameters
----------
filename_or_obj : str or PathLike or file-like object
The file to write to.
Other Parameters
----------------
quality : int
The image quality, on a scale from 1 (worst) to 100 (best).
The default is :rc:`savefig.jpeg_quality`. Values above
95 should be avoided; 100 completely disables the JPEG
quantization stage.
optimize : bool
If present, indicates that the encoder should
make an extra pass over the image in order to select
optimal encoder settings.
progressive : bool
If present, indicates that this image
should be stored as a progressive JPEG file.
"""
buf, size = self.print_to_buffer()
if dryrun:
return
# The image is "pasted" onto a white background image to safely
# handle any transparency
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
rgba = mcolors.to_rgba(rcParams['savefig.facecolor'])
color = tuple([int(x * 255) for x in rgba[:3]])
background = Image.new('RGB', size, color)
background.paste(image, image)
options = {k: kwargs[k]
for k in ['quality', 'optimize', 'progressive', 'dpi']
if k in kwargs}
options.setdefault('quality', rcParams['savefig.jpeg_quality'])
if 'dpi' in options:
# Set the same dpi in both x and y directions
options['dpi'] = (options['dpi'], options['dpi'])
return background.save(filename_or_obj, format='jpeg', **options)
print_jpeg = print_jpg
# add TIFF support
def print_tif(self, filename_or_obj, *args, dryrun=False, **kwargs):
buf, size = self.print_to_buffer()
if dryrun:
return
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
dpi = (self.figure.dpi, self.figure.dpi)
return image.save(filename_or_obj, format='tiff', dpi=dpi)
print_tiff = print_tif
@_Backend.export
class _BackendAgg(_Backend):
FigureCanvas = FigureCanvasAgg
FigureManager = FigureManagerBase
@@ -1,633 +0,0 @@
"""
A Cairo backend for matplotlib
==============================
:Author: Steve Chaplin and others
This backend depends on cairocffi or pycairo.
"""
import copy
import gzip
import sys
import warnings
import numpy as np
# cairocffi is more widely compatible than pycairo (in particular pgi only
# works with cairocffi) so try it first.
try:
import cairocffi as cairo
except ImportError:
try:
import cairo
except ImportError:
raise ImportError("cairo backend requires that cairocffi or pycairo "
"is installed")
else:
if cairo.version_info < (1, 11, 0):
# Introduced create_for_data for Py3.
raise ImportError(
"cairo {} is installed; cairo>=1.11.0 is required"
.format(cairo.version))
backend_version = cairo.version
from .. import cbook
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase)
from matplotlib.font_manager import ttfFontProperty
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
from matplotlib.transforms import Affine2D
if cairo.__name__ == "cairocffi":
# Convert a pycairo context to a cairocffi one.
def _to_context(ctx):
if not isinstance(ctx, cairo.Context):
ctx = cairo.Context._from_pointer(
cairo.ffi.cast(
'cairo_t **',
id(ctx) + object.__basicsize__)[0],
incref=True)
return ctx
else:
# Pass-through a pycairo context.
def _to_context(ctx):
return ctx
@cbook.deprecated("3.0")
class ArrayWrapper:
"""Thin wrapper around numpy ndarray to expose the interface
expected by cairocffi. Basically replicates the
array.array interface.
"""
def __init__(self, myarray):
self.__array = myarray
self.__data = myarray.ctypes.data
self.__size = len(myarray.flatten())
self.itemsize = myarray.itemsize
def buffer_info(self):
return (self.__data, self.__size)
# Mapping from Matplotlib Path codes to cairo path codes.
_MPL_TO_CAIRO_PATH_TYPE = np.zeros(80, dtype=int) # CLOSEPOLY = 79.
_MPL_TO_CAIRO_PATH_TYPE[Path.MOVETO] = cairo.PATH_MOVE_TO
_MPL_TO_CAIRO_PATH_TYPE[Path.LINETO] = cairo.PATH_LINE_TO
_MPL_TO_CAIRO_PATH_TYPE[Path.CURVE4] = cairo.PATH_CURVE_TO
_MPL_TO_CAIRO_PATH_TYPE[Path.CLOSEPOLY] = cairo.PATH_CLOSE_PATH
# Sizes in cairo_path_data_t of each cairo path element.
_CAIRO_PATH_TYPE_SIZES = np.zeros(4, dtype=int)
_CAIRO_PATH_TYPE_SIZES[cairo.PATH_MOVE_TO] = 2
_CAIRO_PATH_TYPE_SIZES[cairo.PATH_LINE_TO] = 2
_CAIRO_PATH_TYPE_SIZES[cairo.PATH_CURVE_TO] = 4
_CAIRO_PATH_TYPE_SIZES[cairo.PATH_CLOSE_PATH] = 1
def _append_paths_slow(ctx, paths, transforms, clip=None):
for path, transform in zip(paths, transforms):
for points, code in path.iter_segments(
transform, remove_nans=True, clip=clip):
if code == Path.MOVETO:
ctx.move_to(*points)
elif code == Path.CLOSEPOLY:
ctx.close_path()
elif code == Path.LINETO:
ctx.line_to(*points)
elif code == Path.CURVE3:
cur = ctx.get_current_point()
ctx.curve_to(
*np.concatenate([cur / 3 + points[:2] * 2 / 3,
points[:2] * 2 / 3 + points[-2:] / 3]))
elif code == Path.CURVE4:
ctx.curve_to(*points)
def _append_paths_fast(ctx, paths, transforms, clip=None):
# We directly convert to the internal representation used by cairo, for
# which ABI compatibility is guaranteed. The layout for each item is
# --CODE(4)-- -LENGTH(4)- ---------PAD(8)---------
# ----------X(8)---------- ----------Y(8)----------
# with the size in bytes in parentheses, and (X, Y) repeated as many times
# as there are points for the current code.
ffi = cairo.ffi
# Convert curves to segment, so that 1. we don't have to handle
# variable-sized CURVE-n codes, and 2. we don't have to implement degree
# elevation for quadratic Beziers.
cleaneds = [path.cleaned(transform, remove_nans=True, clip=clip)
for path, transform in zip(paths, transforms)]
vertices = np.concatenate([cleaned.vertices for cleaned in cleaneds])
codes = np.concatenate([cleaned.codes for cleaned in cleaneds])
# Remove unused vertices and convert to cairo codes. Note that unlike
# cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after
# CLOSE_PATH, so our resulting buffer may be smaller.
vertices = vertices[(codes != Path.STOP) & (codes != Path.CLOSEPOLY)]
codes = codes[codes != Path.STOP]
codes = _MPL_TO_CAIRO_PATH_TYPE[codes]
# Where are the headers of each cairo portions?
cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES[codes]
cairo_type_positions = np.insert(np.cumsum(cairo_type_sizes), 0, 0)
cairo_num_data = cairo_type_positions[-1]
cairo_type_positions = cairo_type_positions[:-1]
# Fill the buffer.
buf = np.empty(cairo_num_data * 16, np.uint8)
as_int = np.frombuffer(buf.data, np.int32)
as_int[::4][cairo_type_positions] = codes
as_int[1::4][cairo_type_positions] = cairo_type_sizes
as_float = np.frombuffer(buf.data, np.float64)
mask = np.ones_like(as_float, bool)
mask[::2][cairo_type_positions] = mask[1::2][cairo_type_positions] = False
as_float[mask] = vertices.ravel()
# Construct the cairo_path_t, and pass it to the context.
ptr = ffi.new("cairo_path_t *")
ptr.status = cairo.STATUS_SUCCESS
ptr.data = ffi.cast("cairo_path_data_t *", ffi.from_buffer(buf))
ptr.num_data = cairo_num_data
cairo.cairo.cairo_append_path(ctx._pointer, ptr)
_append_paths = (_append_paths_fast if cairo.__name__ == "cairocffi"
else _append_paths_slow)
def _append_path(ctx, path, transform, clip=None):
return _append_paths(ctx, [path], [transform], clip)
class RendererCairo(RendererBase):
fontweights = {
100 : cairo.FONT_WEIGHT_NORMAL,
200 : cairo.FONT_WEIGHT_NORMAL,
300 : cairo.FONT_WEIGHT_NORMAL,
400 : cairo.FONT_WEIGHT_NORMAL,
500 : cairo.FONT_WEIGHT_NORMAL,
600 : cairo.FONT_WEIGHT_BOLD,
700 : cairo.FONT_WEIGHT_BOLD,
800 : cairo.FONT_WEIGHT_BOLD,
900 : cairo.FONT_WEIGHT_BOLD,
'ultralight' : cairo.FONT_WEIGHT_NORMAL,
'light' : cairo.FONT_WEIGHT_NORMAL,
'normal' : cairo.FONT_WEIGHT_NORMAL,
'medium' : cairo.FONT_WEIGHT_NORMAL,
'regular' : cairo.FONT_WEIGHT_NORMAL,
'semibold' : cairo.FONT_WEIGHT_BOLD,
'bold' : cairo.FONT_WEIGHT_BOLD,
'heavy' : cairo.FONT_WEIGHT_BOLD,
'ultrabold' : cairo.FONT_WEIGHT_BOLD,
'black' : cairo.FONT_WEIGHT_BOLD,
}
fontangles = {
'italic' : cairo.FONT_SLANT_ITALIC,
'normal' : cairo.FONT_SLANT_NORMAL,
'oblique' : cairo.FONT_SLANT_OBLIQUE,
}
def __init__(self, dpi):
self.dpi = dpi
self.gc = GraphicsContextCairo(renderer=self)
self.text_ctx = cairo.Context(
cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1))
self.mathtext_parser = MathTextParser('Cairo')
RendererBase.__init__(self)
def set_ctx_from_surface(self, surface):
self.gc.ctx = cairo.Context(surface)
# Although it may appear natural to automatically call
# `self.set_width_height(surface.get_width(), surface.get_height())`
# here (instead of having the caller do so separately), this would fail
# for PDF/PS/SVG surfaces, which have no way to report their extents.
def set_width_height(self, width, height):
self.width = width
self.height = height
def _fill_and_stroke(self, ctx, fill_c, alpha, alpha_overrides):
if fill_c is not None:
ctx.save()
if len(fill_c) == 3 or alpha_overrides:
ctx.set_source_rgba(fill_c[0], fill_c[1], fill_c[2], alpha)
else:
ctx.set_source_rgba(fill_c[0], fill_c[1], fill_c[2], fill_c[3])
ctx.fill_preserve()
ctx.restore()
ctx.stroke()
@staticmethod
@cbook.deprecated("3.0")
def convert_path(ctx, path, transform, clip=None):
_append_path(ctx, path, transform, clip)
def draw_path(self, gc, path, transform, rgbFace=None):
ctx = gc.ctx
# Clip the path to the actual rendering extents if it isn't filled.
clip = (ctx.clip_extents()
if rgbFace is None and gc.get_hatch() is None
else None)
transform = (transform
+ Affine2D().scale(1, -1).translate(0, self.height))
ctx.new_path()
_append_path(ctx, path, transform, clip)
self._fill_and_stroke(
ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
def draw_markers(self, gc, marker_path, marker_trans, path, transform,
rgbFace=None):
ctx = gc.ctx
ctx.new_path()
# Create the path for the marker; it needs to be flipped here already!
_append_path(ctx, marker_path, marker_trans + Affine2D().scale(1, -1))
marker_path = ctx.copy_path_flat()
# Figure out whether the path has a fill
x1, y1, x2, y2 = ctx.fill_extents()
if x1 == 0 and y1 == 0 and x2 == 0 and y2 == 0:
filled = False
# No fill, just unset this (so we don't try to fill it later on)
rgbFace = None
else:
filled = True
transform = (transform
+ Affine2D().scale(1, -1).translate(0, self.height))
ctx.new_path()
for i, (vertices, codes) in enumerate(
path.iter_segments(transform, simplify=False)):
if len(vertices):
x, y = vertices[-2:]
ctx.save()
# Translate and apply path
ctx.translate(x, y)
ctx.append_path(marker_path)
ctx.restore()
# Slower code path if there is a fill; we need to draw
# the fill and stroke for each marker at the same time.
# Also flush out the drawing every once in a while to
# prevent the paths from getting way too long.
if filled or i % 1000 == 0:
self._fill_and_stroke(
ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
# Fast path, if there is no fill, draw everything in one step
if not filled:
self._fill_and_stroke(
ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha())
def draw_path_collection(
self, gc, master_transform, paths, all_transforms, offsets,
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
antialiaseds, urls, offset_position):
path_ids = []
for path, transform in self._iter_collection_raw_paths(
master_transform, paths, all_transforms):
path_ids.append((path, Affine2D(transform)))
reuse_key = None
grouped_draw = []
def _draw_paths():
if not grouped_draw:
return
gc_vars, rgb_fc = reuse_key
gc = copy.copy(gc0)
# We actually need to call the setters to reset the internal state.
vars(gc).update(gc_vars)
for k, v in gc_vars.items():
if k == "_linestyle": # Deprecated, no effect.
continue
try:
getattr(gc, "set" + k)(v)
except (AttributeError, TypeError) as e:
pass
gc.ctx.new_path()
paths, transforms = zip(*grouped_draw)
grouped_draw.clear()
_append_paths(gc.ctx, paths, transforms)
self._fill_and_stroke(
gc.ctx, rgb_fc, gc.get_alpha(), gc.get_forced_alpha())
for xo, yo, path_id, gc0, rgb_fc 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 = (Affine2D(transform.get_matrix())
.translate(xo, yo - self.height).scale(1, -1))
# rgb_fc could be a ndarray, for which equality is elementwise.
new_key = vars(gc0), tuple(rgb_fc) if rgb_fc is not None else None
if new_key == reuse_key:
grouped_draw.append((path, transform))
else:
_draw_paths()
grouped_draw.append((path, transform))
reuse_key = new_key
_draw_paths()
def draw_image(self, gc, x, y, im):
im = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(im[::-1])
surface = cairo.ImageSurface.create_for_data(
im.ravel().data, cairo.FORMAT_ARGB32,
im.shape[1], im.shape[0], im.shape[1] * 4)
ctx = gc.ctx
y = self.height - y - im.shape[0]
ctx.save()
ctx.set_source_surface(surface, float(x), float(y))
ctx.paint()
ctx.restore()
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
# Note: x,y are device/display coords, not user-coords, unlike other
# draw_* methods
if ismath:
self._draw_mathtext(gc, x, y, s, prop, angle)
else:
ctx = gc.ctx
ctx.new_path()
ctx.move_to(x, y)
ctx.select_font_face(prop.get_name(),
self.fontangles[prop.get_style()],
self.fontweights[prop.get_weight()])
size = prop.get_size_in_points() * self.dpi / 72.0
ctx.save()
if angle:
ctx.rotate(np.deg2rad(-angle))
ctx.set_font_size(size)
ctx.show_text(s)
ctx.restore()
def _draw_mathtext(self, gc, x, y, s, prop, angle):
ctx = gc.ctx
width, height, descent, glyphs, rects = self.mathtext_parser.parse(
s, self.dpi, prop)
ctx.save()
ctx.translate(x, y)
if angle:
ctx.rotate(np.deg2rad(-angle))
for font, fontsize, s, ox, oy in glyphs:
ctx.new_path()
ctx.move_to(ox, oy)
fontProp = ttfFontProperty(font)
ctx.select_font_face(fontProp.name,
self.fontangles[fontProp.style],
self.fontweights[fontProp.weight])
size = fontsize * self.dpi / 72.0
ctx.set_font_size(size)
ctx.show_text(s)
for ox, oy, w, h in rects:
ctx.new_path()
ctx.rectangle(ox, oy, w, h)
ctx.set_source_rgb(0, 0, 0)
ctx.fill_preserve()
ctx.restore()
def get_canvas_width_height(self):
return self.width, self.height
def get_text_width_height_descent(self, s, prop, ismath):
if ismath:
width, height, descent, fonts, used_characters = \
self.mathtext_parser.parse(s, self.dpi, prop)
return width, height, descent
ctx = self.text_ctx
ctx.save()
ctx.select_font_face(prop.get_name(),
self.fontangles[prop.get_style()],
self.fontweights[prop.get_weight()])
# Cairo (says it) uses 1/96 inch user space units, ref: cairo_gstate.c
# but if /96.0 is used the font is too small
size = prop.get_size_in_points() * self.dpi / 72
# problem - scale remembers last setting and font can become
# enormous causing program to crash
# save/restore prevents the problem
ctx.set_font_size(size)
y_bearing, w, h = ctx.text_extents(s)[1:4]
ctx.restore()
return w, h, h + y_bearing
def new_gc(self):
self.gc.ctx.save()
self.gc._alpha = 1
self.gc._forced_alpha = False # if True, _alpha overrides A from RGBA
return self.gc
def points_to_pixels(self, points):
return points / 72 * self.dpi
class GraphicsContextCairo(GraphicsContextBase):
_joind = {
'bevel' : cairo.LINE_JOIN_BEVEL,
'miter' : cairo.LINE_JOIN_MITER,
'round' : cairo.LINE_JOIN_ROUND,
}
_capd = {
'butt' : cairo.LINE_CAP_BUTT,
'projecting' : cairo.LINE_CAP_SQUARE,
'round' : cairo.LINE_CAP_ROUND,
}
def __init__(self, renderer):
GraphicsContextBase.__init__(self)
self.renderer = renderer
def restore(self):
self.ctx.restore()
def set_alpha(self, alpha):
GraphicsContextBase.set_alpha(self, alpha)
_alpha = self.get_alpha()
rgb = self._rgb
if self.get_forced_alpha():
self.ctx.set_source_rgba(rgb[0], rgb[1], rgb[2], _alpha)
else:
self.ctx.set_source_rgba(rgb[0], rgb[1], rgb[2], rgb[3])
# def set_antialiased(self, b):
# cairo has many antialiasing modes, we need to pick one for True and
# one for False.
def set_capstyle(self, cs):
if cs in ('butt', 'round', 'projecting'):
self._capstyle = cs
self.ctx.set_line_cap(self._capd[cs])
else:
raise ValueError('Unrecognized cap style. Found %s' % cs)
def set_clip_rectangle(self, rectangle):
if not rectangle:
return
x, y, w, h = np.round(rectangle.bounds)
ctx = self.ctx
ctx.new_path()
ctx.rectangle(x, self.renderer.height - h - y, w, h)
ctx.clip()
def set_clip_path(self, path):
if not path:
return
tpath, affine = path.get_transformed_path_and_affine()
ctx = self.ctx
ctx.new_path()
affine = (affine
+ Affine2D().scale(1, -1).translate(0, self.renderer.height))
_append_path(ctx, tpath, affine)
ctx.clip()
def set_dashes(self, offset, dashes):
self._dashes = offset, dashes
if dashes is None:
self.ctx.set_dash([], 0) # switch dashes off
else:
self.ctx.set_dash(
list(self.renderer.points_to_pixels(np.asarray(dashes))),
offset)
def set_foreground(self, fg, isRGBA=None):
GraphicsContextBase.set_foreground(self, fg, isRGBA)
if len(self._rgb) == 3:
self.ctx.set_source_rgb(*self._rgb)
else:
self.ctx.set_source_rgba(*self._rgb)
def get_rgb(self):
return self.ctx.get_source().get_rgba()[:3]
def set_joinstyle(self, js):
if js in ('miter', 'round', 'bevel'):
self._joinstyle = js
self.ctx.set_line_join(self._joind[js])
else:
raise ValueError('Unrecognized join style. Found %s' % js)
def set_linewidth(self, w):
self._linewidth = float(w)
self.ctx.set_line_width(self.renderer.points_to_pixels(w))
class FigureCanvasCairo(FigureCanvasBase):
supports_blit = False
def print_png(self, fobj, *args, **kwargs):
self._get_printed_image_surface().write_to_png(fobj)
def print_rgba(self, fobj, *args, **kwargs):
width, height = self.get_width_height()
buf = self._get_printed_image_surface().get_data()
fobj.write(cbook._premultiplied_argb32_to_unmultiplied_rgba8888(
np.asarray(buf).reshape((width, height, 4))))
print_raw = print_rgba
def _get_printed_image_surface(self):
width, height = self.get_width_height()
renderer = RendererCairo(self.figure.dpi)
renderer.set_width_height(width, height)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
renderer.set_ctx_from_surface(surface)
self.figure.draw(renderer)
return surface
def print_pdf(self, fobj, *args, **kwargs):
return self._save(fobj, 'pdf', *args, **kwargs)
def print_ps(self, fobj, *args, **kwargs):
return self._save(fobj, 'ps', *args, **kwargs)
def print_svg(self, fobj, *args, **kwargs):
return self._save(fobj, 'svg', *args, **kwargs)
def print_svgz(self, fobj, *args, **kwargs):
return self._save(fobj, 'svgz', *args, **kwargs)
def _save(self, fo, fmt, **kwargs):
# save PDF/PS/SVG
orientation = kwargs.get('orientation', 'portrait')
dpi = 72
self.figure.dpi = dpi
w_in, h_in = self.figure.get_size_inches()
width_in_points, height_in_points = w_in * dpi, h_in * dpi
if orientation == 'landscape':
width_in_points, height_in_points = (
height_in_points, width_in_points)
if fmt == 'ps':
if not hasattr(cairo, 'PSSurface'):
raise RuntimeError('cairo has not been compiled with PS '
'support enabled')
surface = cairo.PSSurface(fo, width_in_points, height_in_points)
elif fmt == 'pdf':
if not hasattr(cairo, 'PDFSurface'):
raise RuntimeError('cairo has not been compiled with PDF '
'support enabled')
surface = cairo.PDFSurface(fo, width_in_points, height_in_points)
elif fmt in ('svg', 'svgz'):
if not hasattr(cairo, 'SVGSurface'):
raise RuntimeError('cairo has not been compiled with SVG '
'support enabled')
if fmt == 'svgz':
if isinstance(fo, str):
fo = gzip.GzipFile(fo, 'wb')
else:
fo = gzip.GzipFile(None, 'wb', fileobj=fo)
surface = cairo.SVGSurface(fo, width_in_points, height_in_points)
else:
warnings.warn("unknown format: %s" % fmt, stacklevel=2)
return
# surface.set_dpi() can be used
renderer = RendererCairo(self.figure.dpi)
renderer.set_width_height(width_in_points, height_in_points)
renderer.set_ctx_from_surface(surface)
ctx = renderer.gc.ctx
if orientation == 'landscape':
ctx.rotate(np.pi / 2)
ctx.translate(0, -height_in_points)
# Perhaps add an '%%Orientation: Landscape' comment?
self.figure.draw(renderer)
ctx.show_page()
surface.finish()
if fmt == 'svgz':
fo.close()
@_Backend.export
class _BackendCairo(_Backend):
FigureCanvas = FigureCanvasCairo
FigureManager = FigureManagerBase
@@ -1,990 +0,0 @@
import logging
import os
import sys
import matplotlib
from matplotlib import backend_tools, cbook, rcParams
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
StatusbarBase, TimerBase, ToolContainerBase, cursors)
from matplotlib.backend_managers import ToolManager
from matplotlib.figure import Figure
from matplotlib.widgets import SubplotTool
from ._gtk3_compat import GLib, GObject, Gtk, Gdk
_log = logging.getLogger(__name__)
backend_version = "%s.%s.%s" % (
Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version())
# the true dots per inch on the screen; should be display dependent
# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi
PIXELS_PER_INCH = 96
try:
cursord = {
cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR),
cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2),
cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR),
cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS),
cursors.WAIT : Gdk.Cursor.new(Gdk.CursorType.WATCH),
}
except TypeError as exc:
# Happens when running headless. Convert to ImportError to cooperate with
# backend switching.
raise ImportError(exc)
class TimerGTK3(TimerBase):
'''
Subclass of :class:`backend_bases.TimerBase` using GTK3 for timer events.
Attributes
----------
interval : int
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
Stores list of (func, args) 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 _timer_start(self):
# Need to stop it, otherwise we potentially leak a timer id that will
# never be stopped.
self._timer_stop()
self._timer = GLib.timeout_add(self._interval, self._on_timer)
def _timer_stop(self):
if self._timer is not None:
GLib.source_remove(self._timer)
self._timer = None
def _timer_set_interval(self):
# Only stop and restart it if the timer has already been started
if self._timer is not None:
self._timer_stop()
self._timer_start()
def _on_timer(self):
TimerBase._on_timer(self)
# Gtk timeout_add() requires that the callback returns True if it
# is to be called again.
if self.callbacks and not self._single:
return True
else:
self._timer = None
return False
class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase):
keyvald = {65507: 'control',
65505: 'shift',
65513: 'alt',
65508: 'control',
65506: 'shift',
65514: 'alt',
65361: 'left',
65362: 'up',
65363: 'right',
65364: 'down',
65307: 'escape',
65470: 'f1',
65471: 'f2',
65472: 'f3',
65473: 'f4',
65474: 'f5',
65475: 'f6',
65476: 'f7',
65477: 'f8',
65478: 'f9',
65479: 'f10',
65480: 'f11',
65481: 'f12',
65300: 'scroll_lock',
65299: 'break',
65288: 'backspace',
65293: 'enter',
65379: 'insert',
65535: 'delete',
65360: 'home',
65367: 'end',
65365: 'pageup',
65366: 'pagedown',
65438: '0',
65436: '1',
65433: '2',
65435: '3',
65430: '4',
65437: '5',
65432: '6',
65429: '7',
65431: '8',
65434: '9',
65451: '+',
65453: '-',
65450: '*',
65455: '/',
65439: 'dec',
65421: 'enter',
}
# Setting this as a static constant prevents
# this resulting expression from leaking
event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK |
Gdk.EventMask.EXPOSURE_MASK |
Gdk.EventMask.KEY_PRESS_MASK |
Gdk.EventMask.KEY_RELEASE_MASK |
Gdk.EventMask.ENTER_NOTIFY_MASK |
Gdk.EventMask.LEAVE_NOTIFY_MASK |
Gdk.EventMask.POINTER_MOTION_MASK |
Gdk.EventMask.POINTER_MOTION_HINT_MASK|
Gdk.EventMask.SCROLL_MASK)
def __init__(self, figure):
FigureCanvasBase.__init__(self, figure)
GObject.GObject.__init__(self)
self._idle_draw_id = 0
self._lastCursor = None
self.connect('scroll_event', self.scroll_event)
self.connect('button_press_event', self.button_press_event)
self.connect('button_release_event', self.button_release_event)
self.connect('configure_event', self.configure_event)
self.connect('draw', self.on_draw_event)
self.connect('key_press_event', self.key_press_event)
self.connect('key_release_event', self.key_release_event)
self.connect('motion_notify_event', self.motion_notify_event)
self.connect('leave_notify_event', self.leave_notify_event)
self.connect('enter_notify_event', self.enter_notify_event)
self.connect('size_allocate', self.size_allocate)
self.set_events(self.__class__.event_mask)
self.set_double_buffered(True)
self.set_can_focus(True)
self._renderer_init()
default_context = GLib.main_context_get_thread_default() or GLib.main_context_default()
def destroy(self):
#Gtk.DrawingArea.destroy(self)
self.close_event()
if self._idle_draw_id != 0:
GLib.source_remove(self._idle_draw_id)
def scroll_event(self, widget, event):
x = event.x
# flipy so y=0 is bottom of canvas
y = self.get_allocation().height - event.y
if event.direction==Gdk.ScrollDirection.UP:
step = 1
else:
step = -1
FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)
return False # finish event propagation?
def button_press_event(self, widget, event):
x = event.x
# flipy so y=0 is bottom of canvas
y = self.get_allocation().height - event.y
FigureCanvasBase.button_press_event(self, x, y, event.button, guiEvent=event)
return False # finish event propagation?
def button_release_event(self, widget, event):
x = event.x
# flipy so y=0 is bottom of canvas
y = self.get_allocation().height - event.y
FigureCanvasBase.button_release_event(self, x, y, event.button, guiEvent=event)
return False # finish event propagation?
def key_press_event(self, widget, event):
key = self._get_key(event)
FigureCanvasBase.key_press_event(self, key, guiEvent=event)
return True # stop event propagation
def key_release_event(self, widget, event):
key = self._get_key(event)
FigureCanvasBase.key_release_event(self, key, guiEvent=event)
return True # stop event propagation
def motion_notify_event(self, widget, event):
if event.is_hint:
t, x, y, state = event.window.get_pointer()
else:
x, y, state = event.x, event.y, event.get_state()
# flipy so y=0 is bottom of canvas
y = self.get_allocation().height - y
FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)
return False # finish event propagation?
def leave_notify_event(self, widget, event):
FigureCanvasBase.leave_notify_event(self, event)
def enter_notify_event(self, widget, event):
x = event.x
# flipy so y=0 is bottom of canvas
y = self.get_allocation().height - event.y
FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y))
def size_allocate(self, widget, allocation):
dpival = self.figure.dpi
winch = allocation.width / dpival
hinch = allocation.height / dpival
self.figure.set_size_inches(winch, hinch, forward=False)
FigureCanvasBase.resize_event(self)
self.draw_idle()
def _get_key(self, event):
if event.keyval in self.keyvald:
key = self.keyvald[event.keyval]
elif event.keyval < 256:
key = chr(event.keyval)
else:
key = None
modifiers = [
(Gdk.ModifierType.MOD4_MASK, 'super'),
(Gdk.ModifierType.MOD1_MASK, 'alt'),
(Gdk.ModifierType.CONTROL_MASK, 'ctrl'),
]
for key_mask, prefix in modifiers:
if event.state & key_mask:
key = '{0}+{1}'.format(prefix, key)
return key
def configure_event(self, widget, event):
if widget.get_property("window") is None:
return
w, h = event.width, event.height
if w < 3 or h < 3:
return # empty fig
# resize the figure (in inches)
dpi = self.figure.dpi
self.figure.set_size_inches(w / dpi, h / dpi, forward=False)
return False # finish event propagation?
def on_draw_event(self, widget, ctx):
# to be overwritten by GTK3Agg or GTK3Cairo
pass
def draw(self):
if self.get_visible() and self.get_mapped():
self.queue_draw()
# do a synchronous draw (its less efficient than an async draw,
# but is required if/when animation is used)
self.get_property("window").process_updates(False)
def draw_idle(self):
if self._idle_draw_id != 0:
return
def idle_draw(*args):
try:
self.draw()
finally:
self._idle_draw_id = 0
return False
self._idle_draw_id = GLib.idle_add(idle_draw)
def new_timer(self, *args, **kwargs):
"""
Creates a new backend-specific subclass of :class:`backend_bases.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
Sequence of (func, args, kwargs) where ``func(*args, **kwargs)``
will be executed by the timer every *interval*.
"""
return TimerGTK3(*args, **kwargs)
def flush_events(self):
Gdk.threads_enter()
while Gtk.events_pending():
Gtk.main_iteration()
Gdk.flush()
Gdk.threads_leave()
class FigureManagerGTK3(FigureManagerBase):
"""
Attributes
----------
canvas : `FigureCanvas`
The FigureCanvas instance
num : int or str
The Figure number
toolbar : Gtk.Toolbar
The Gtk.Toolbar
vbox : Gtk.VBox
The Gtk.VBox containing the canvas and toolbar
window : Gtk.Window
The Gtk.Window
"""
def __init__(self, canvas, num):
FigureManagerBase.__init__(self, canvas, num)
self.window = Gtk.Window()
self.window.set_wmclass("matplotlib", "Matplotlib")
self.set_window_title("Figure %d" % num)
try:
self.window.set_icon_from_file(window_icon)
except Exception:
# Some versions of gtk throw a glib.GError but not all, so I am not
# sure how to catch it. I am unhappy doing a blanket catch here,
# but am not sure what a better way is - JDH
_log.info('Could not load matplotlib icon: %s', sys.exc_info()[1])
self.vbox = Gtk.Box()
self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
self.window.add(self.vbox)
self.vbox.show()
self.canvas.show()
self.vbox.pack_start(self.canvas, True, True, 0)
# calculate size for window
w = int(self.canvas.figure.bbox.width)
h = int(self.canvas.figure.bbox.height)
self.toolmanager = self._get_toolmanager()
self.toolbar = self._get_toolbar()
self.statusbar = None
def add_widget(child, expand, fill, padding):
child.show()
self.vbox.pack_end(child, False, False, 0)
size_request = child.size_request()
return size_request.height
if self.toolmanager:
backend_tools.add_tools_to_manager(self.toolmanager)
if self.toolbar:
backend_tools.add_tools_to_container(self.toolbar)
self.statusbar = StatusbarGTK3(self.toolmanager)
h += add_widget(self.statusbar, False, False, 0)
h += add_widget(Gtk.HSeparator(), False, False, 0)
if self.toolbar is not None:
self.toolbar.show()
h += add_widget(self.toolbar, False, False, 0)
self.window.set_default_size(w, h)
def destroy(*args):
Gcf.destroy(num)
self.window.connect("destroy", destroy)
self.window.connect("delete_event", destroy)
if matplotlib.is_interactive():
self.window.show()
self.canvas.draw_idle()
self.canvas.grab_focus()
def destroy(self, *args):
self.vbox.destroy()
self.window.destroy()
self.canvas.destroy()
if self.toolbar:
self.toolbar.destroy()
if (Gcf.get_num_fig_managers() == 0 and
not matplotlib.is_interactive() and
Gtk.main_level() >= 1):
Gtk.main_quit()
def show(self):
# show the figure window
self.window.show()
self.window.present()
def full_screen_toggle(self):
self._full_screen_flag = not self._full_screen_flag
if self._full_screen_flag:
self.window.fullscreen()
else:
self.window.unfullscreen()
_full_screen_flag = False
def _get_toolbar(self):
# must be inited after the window, drawingArea and figure
# attrs are set
if rcParams['toolbar'] == 'toolbar2':
toolbar = NavigationToolbar2GTK3(self.canvas, self.window)
elif rcParams['toolbar'] == 'toolmanager':
toolbar = ToolbarGTK3(self.toolmanager)
else:
toolbar = None
return toolbar
def _get_toolmanager(self):
# must be initialised after toolbar has been set
if rcParams['toolbar'] == 'toolmanager':
toolmanager = ToolManager(self.canvas.figure)
else:
toolmanager = None
return toolmanager
def get_window_title(self):
return self.window.get_title()
def set_window_title(self, title):
self.window.set_title(title)
def resize(self, width, height):
'set the canvas size in pixels'
#_, _, cw, ch = self.canvas.allocation
#_, _, ww, wh = self.window.allocation
#self.window.resize (width-cw+ww, height-ch+wh)
self.window.resize(width, height)
class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar):
def __init__(self, canvas, window):
self.win = window
GObject.GObject.__init__(self)
NavigationToolbar2.__init__(self, canvas)
self.ctx = None
def set_message(self, s):
self.message.set_label(s)
def set_cursor(self, cursor):
self.canvas.get_property("window").set_cursor(cursord[cursor])
Gtk.main_iteration()
def draw_rubberband(self, event, x0, y0, x1, y1):
'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744'
self.ctx = self.canvas.get_property("window").cairo_create()
# todo: instead of redrawing the entire figure, copy the part of
# the figure that was covered by the previous rubberband rectangle
self.canvas.draw()
height = self.canvas.figure.bbox.height
y1 = height - y1
y0 = height - y0
w = abs(x1 - x0)
h = abs(y1 - y0)
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
self.ctx.new_path()
self.ctx.set_line_width(0.5)
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
self.ctx.set_source_rgb(0, 0, 0)
self.ctx.stroke()
def _init_toolbar(self):
self.set_style(Gtk.ToolbarStyle.ICONS)
basedir = os.path.join(rcParams['datapath'], 'images')
for text, tooltip_text, image_file, callback in self.toolitems:
if text is None:
self.insert(Gtk.SeparatorToolItem(), -1)
continue
fname = os.path.join(basedir, image_file + '.png')
image = Gtk.Image()
image.set_from_file(fname)
tbutton = Gtk.ToolButton()
tbutton.set_label(text)
tbutton.set_icon_widget(image)
self.insert(tbutton, -1)
tbutton.connect('clicked', getattr(self, callback))
tbutton.set_tooltip_text(tooltip_text)
toolitem = Gtk.SeparatorToolItem()
self.insert(toolitem, -1)
toolitem.set_draw(False)
toolitem.set_expand(True)
toolitem = Gtk.ToolItem()
self.insert(toolitem, -1)
self.message = Gtk.Label()
toolitem.add(self.message)
self.show_all()
def get_filechooser(self):
fc = FileChooserDialog(
title='Save the figure',
parent=self.win,
path=os.path.expanduser(rcParams['savefig.directory']),
filetypes=self.canvas.get_supported_filetypes(),
default_filetype=self.canvas.get_default_filetype())
fc.set_current_name(self.canvas.get_default_filename())
return fc
def save_figure(self, *args):
chooser = self.get_filechooser()
fname, format = chooser.get_filename_from_user()
chooser.destroy()
if fname:
startpath = os.path.expanduser(rcParams['savefig.directory'])
# Save dir for next time, unless empty str (i.e., use cwd).
if startpath != "":
rcParams['savefig.directory'] = os.path.dirname(fname)
try:
self.canvas.figure.savefig(fname, format=format)
except Exception as e:
error_msg_gtk(str(e), parent=self)
def configure_subplots(self, button):
toolfig = Figure(figsize=(6, 3))
canvas = self._get_canvas(toolfig)
toolfig.subplots_adjust(top=0.9)
tool = SubplotTool(self.canvas.figure, toolfig)
w = int(toolfig.bbox.width)
h = int(toolfig.bbox.height)
window = Gtk.Window()
try:
window.set_icon_from_file(window_icon)
except Exception:
# we presumably already logged a message on the
# failure of the main plot, don't keep reporting
pass
window.set_title("Subplot Configuration Tool")
window.set_default_size(w, h)
vbox = Gtk.Box()
vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
window.add(vbox)
vbox.show()
canvas.show()
vbox.pack_start(canvas, True, True, 0)
window.show()
def _get_canvas(self, fig):
return self.canvas.__class__(fig)
class FileChooserDialog(Gtk.FileChooserDialog):
"""GTK+ file selector which remembers the last file/directory
selected and presents the user with a menu of supported image formats
"""
def __init__(self,
title = 'Save file',
parent = None,
action = Gtk.FileChooserAction.SAVE,
buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
path = None,
filetypes = [],
default_filetype = None
):
super().__init__(title, parent, action, buttons)
self.set_default_response(Gtk.ResponseType.OK)
self.set_do_overwrite_confirmation(True)
if not path:
path = os.getcwd()
# create an extra widget to list supported image formats
self.set_current_folder(path)
self.set_current_name('image.' + default_filetype)
hbox = Gtk.Box(spacing=10)
hbox.pack_start(Gtk.Label(label="File Format:"), False, False, 0)
liststore = Gtk.ListStore(GObject.TYPE_STRING)
cbox = Gtk.ComboBox()
cbox.set_model(liststore)
cell = Gtk.CellRendererText()
cbox.pack_start(cell, True)
cbox.add_attribute(cell, 'text', 0)
hbox.pack_start(cbox, False, False, 0)
self.filetypes = filetypes
sorted_filetypes = sorted(filetypes.items())
default = 0
for i, (ext, name) in enumerate(sorted_filetypes):
liststore.append(["%s (*.%s)" % (name, ext)])
if ext == default_filetype:
default = i
cbox.set_active(default)
self.ext = default_filetype
def cb_cbox_changed(cbox, data=None):
"""File extension changed"""
head, filename = os.path.split(self.get_filename())
root, ext = os.path.splitext(filename)
ext = ext[1:]
new_ext = sorted_filetypes[cbox.get_active()][0]
self.ext = new_ext
if ext in self.filetypes:
filename = root + '.' + new_ext
elif ext == '':
filename = filename.rstrip('.') + '.' + new_ext
self.set_current_name(filename)
cbox.connect("changed", cb_cbox_changed)
hbox.show_all()
self.set_extra_widget(hbox)
@cbook.deprecated("3.0", alternative="sorted(self.filetypes.items())")
def sorted_filetypes(self):
return sorted(self.filetypes.items())
def get_filename_from_user(self):
if self.run() == int(Gtk.ResponseType.OK):
return self.get_filename(), self.ext
else:
return None, self.ext
class RubberbandGTK3(backend_tools.RubberbandBase):
def __init__(self, *args, **kwargs):
backend_tools.RubberbandBase.__init__(self, *args, **kwargs)
self.ctx = None
def draw_rubberband(self, x0, y0, x1, y1):
# 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/
# Recipe/189744'
self.ctx = self.figure.canvas.get_property("window").cairo_create()
# todo: instead of redrawing the entire figure, copy the part of
# the figure that was covered by the previous rubberband rectangle
self.figure.canvas.draw()
height = self.figure.bbox.height
y1 = height - y1
y0 = height - y0
w = abs(x1 - x0)
h = abs(y1 - y0)
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
self.ctx.new_path()
self.ctx.set_line_width(0.5)
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
self.ctx.set_source_rgb(0, 0, 0)
self.ctx.stroke()
class ToolbarGTK3(ToolContainerBase, Gtk.Box):
_icon_extension = '.png'
def __init__(self, toolmanager):
ToolContainerBase.__init__(self, toolmanager)
Gtk.Box.__init__(self)
self.set_property("orientation", Gtk.Orientation.VERTICAL)
self._toolarea = Gtk.Box()
self._toolarea.set_property('orientation', Gtk.Orientation.HORIZONTAL)
self.pack_start(self._toolarea, False, False, 0)
self._toolarea.show_all()
self._groups = {}
self._toolitems = {}
def add_toolitem(self, name, group, position, image_file, description,
toggle):
if toggle:
tbutton = Gtk.ToggleToolButton()
else:
tbutton = Gtk.ToolButton()
tbutton.set_label(name)
if image_file is not None:
image = Gtk.Image()
image.set_from_file(image_file)
tbutton.set_icon_widget(image)
if position is None:
position = -1
self._add_button(tbutton, group, position)
signal = tbutton.connect('clicked', self._call_tool, name)
tbutton.set_tooltip_text(description)
tbutton.show_all()
self._toolitems.setdefault(name, [])
self._toolitems[name].append((tbutton, signal))
def _add_button(self, button, group, position):
if group not in self._groups:
if self._groups:
self._add_separator()
toolbar = Gtk.Toolbar()
toolbar.set_style(Gtk.ToolbarStyle.ICONS)
self._toolarea.pack_start(toolbar, False, False, 0)
toolbar.show_all()
self._groups[group] = toolbar
self._groups[group].insert(button, position)
def _call_tool(self, btn, name):
self.trigger_tool(name)
def toggle_toolitem(self, name, toggled):
if name not in self._toolitems:
return
for toolitem, signal in self._toolitems[name]:
toolitem.handler_block(signal)
toolitem.set_active(toggled)
toolitem.handler_unblock(signal)
def remove_toolitem(self, name):
if name not in self._toolitems:
self.toolmanager.message_event('%s Not in toolbar' % name, self)
return
for group in self._groups:
for toolitem, _signal in self._toolitems[name]:
if toolitem in self._groups[group]:
self._groups[group].remove(toolitem)
del self._toolitems[name]
def _add_separator(self):
sep = Gtk.Separator()
sep.set_property("orientation", Gtk.Orientation.VERTICAL)
self._toolarea.pack_start(sep, False, True, 0)
sep.show_all()
class StatusbarGTK3(StatusbarBase, Gtk.Statusbar):
def __init__(self, *args, **kwargs):
StatusbarBase.__init__(self, *args, **kwargs)
Gtk.Statusbar.__init__(self)
self._context = self.get_context_id('message')
def set_message(self, s):
self.pop(self._context)
self.push(self._context, s)
class SaveFigureGTK3(backend_tools.SaveFigureBase):
def get_filechooser(self):
fc = FileChooserDialog(
title='Save the figure',
parent=self.figure.canvas.manager.window,
path=os.path.expanduser(rcParams['savefig.directory']),
filetypes=self.figure.canvas.get_supported_filetypes(),
default_filetype=self.figure.canvas.get_default_filetype())
fc.set_current_name(self.figure.canvas.get_default_filename())
return fc
def trigger(self, *args, **kwargs):
chooser = self.get_filechooser()
fname, format_ = chooser.get_filename_from_user()
chooser.destroy()
if fname:
startpath = os.path.expanduser(rcParams['savefig.directory'])
if startpath == '':
# explicitly missing key or empty str signals to use cwd
rcParams['savefig.directory'] = startpath
else:
# save dir for next time
rcParams['savefig.directory'] = os.path.dirname(fname)
try:
self.figure.canvas.print_figure(fname, format=format_)
except Exception as e:
error_msg_gtk(str(e), parent=self)
class SetCursorGTK3(backend_tools.SetCursorBase):
def set_cursor(self, cursor):
self.figure.canvas.get_property("window").set_cursor(cursord[cursor])
class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window):
def __init__(self, *args, **kwargs):
backend_tools.ConfigureSubplotsBase.__init__(self, *args, **kwargs)
self.window = None
def init_window(self):
if self.window:
return
self.window = Gtk.Window(title="Subplot Configuration Tool")
try:
self.window.window.set_icon_from_file(window_icon)
except Exception:
# we presumably already logged a message on the
# failure of the main plot, don't keep reporting
pass
self.vbox = Gtk.Box()
self.vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
self.window.add(self.vbox)
self.vbox.show()
self.window.connect('destroy', self.destroy)
toolfig = Figure(figsize=(6, 3))
canvas = self.figure.canvas.__class__(toolfig)
toolfig.subplots_adjust(top=0.9)
SubplotTool(self.figure, toolfig)
w = int(toolfig.bbox.width)
h = int(toolfig.bbox.height)
self.window.set_default_size(w, h)
canvas.show()
self.vbox.pack_start(canvas, True, True, 0)
self.window.show()
def destroy(self, *args):
self.window.destroy()
self.window = None
def _get_canvas(self, fig):
return self.canvas.__class__(fig)
def trigger(self, sender, event, data=None):
self.init_window()
self.window.present()
class HelpGTK3(backend_tools.ToolHelpBase):
def _normalize_shortcut(self, key):
"""
Convert Matplotlib key presses to GTK+ accelerator identifiers.
Related to `FigureCanvasGTK3._get_key`.
"""
special = {
'backspace': 'BackSpace',
'pagedown': 'Page_Down',
'pageup': 'Page_Up',
'scroll_lock': 'Scroll_Lock',
}
parts = key.split('+')
mods = ['<' + mod + '>' for mod in parts[:-1]]
key = parts[-1]
if key in special:
key = special[key]
elif len(key) > 1:
key = key.capitalize()
elif key.isupper():
mods += ['<shift>']
return ''.join(mods) + key
def _show_shortcuts_window(self):
section = Gtk.ShortcutsSection()
for name, tool in sorted(self.toolmanager.tools.items()):
if not tool.description:
continue
# Putting everything in a separate group allows GTK to
# automatically split them into separate columns/pages, which is
# useful because we have lots of shortcuts, some with many keys
# that are very wide.
group = Gtk.ShortcutsGroup()
section.add(group)
# A hack to remove the title since we have no group naming.
group.forall(lambda widget, data: widget.set_visible(False), None)
shortcut = Gtk.ShortcutsShortcut(
accelerator=' '.join(
self._normalize_shortcut(key)
for key in self.toolmanager.get_tool_keymap(name)
# Will never be sent:
if 'cmd+' not in key),
title=tool.name,
subtitle=tool.description)
group.add(shortcut)
window = Gtk.ShortcutsWindow(
title='Help',
modal=True,
transient_for=self._figure.canvas.get_toplevel())
section.show() # Must be done explicitly before add!
window.add(section)
window.show_all()
def _show_shortcuts_dialog(self):
dialog = Gtk.MessageDialog(
self._figure.canvas.get_toplevel(),
0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, self._get_help_text(),
title="Help")
dialog.run()
dialog.destroy()
def trigger(self, *args):
if Gtk.check_version(3, 20, 0) is None:
self._show_shortcuts_window()
else:
self._show_shortcuts_dialog()
class ToolCopyToClipboardGTK3(backend_tools.ToolCopyToClipboardBase):
def trigger(self, *args, **kwargs):
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
window = self.canvas.get_window()
x, y, width, height = window.get_geometry()
pb = Gdk.pixbuf_get_from_window(window, x, y, width, height)
clipboard.set_image(pb)
# Define the file to use as the GTk icon
if sys.platform == 'win32':
icon_filename = 'matplotlib.png'
else:
icon_filename = 'matplotlib.svg'
window_icon = os.path.join(
matplotlib.rcParams['datapath'], 'images', icon_filename)
def error_msg_gtk(msg, parent=None):
if parent is not None: # find the toplevel Gtk.Window
parent = parent.get_toplevel()
if not parent.is_toplevel():
parent = None
if not isinstance(msg, str):
msg = ','.join(map(str, msg))
dialog = Gtk.MessageDialog(
parent = parent,
type = Gtk.MessageType.ERROR,
buttons = Gtk.ButtonsType.OK,
message_format = msg)
dialog.run()
dialog.destroy()
backend_tools.ToolSaveFigure = SaveFigureGTK3
backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3
backend_tools.ToolSetCursor = SetCursorGTK3
backend_tools.ToolRubberband = RubberbandGTK3
backend_tools.ToolHelp = HelpGTK3
backend_tools.ToolCopyToClipboard = ToolCopyToClipboardGTK3
Toolbar = ToolbarGTK3
@_Backend.export
class _BackendGTK3(_Backend):
required_interactive_framework = "gtk3"
FigureCanvas = FigureCanvasGTK3
FigureManager = FigureManagerGTK3
@staticmethod
def trigger_manager_draw(manager):
manager.canvas.draw_idle()
@staticmethod
def mainloop():
if Gtk.main_level() == 0:
Gtk.main()
@@ -1,90 +0,0 @@
import sys
import numpy as np
from .. import cbook
from . import backend_agg, backend_cairo, backend_gtk3
from ._gtk3_compat import gi
from .backend_cairo import cairo
from .backend_gtk3 import Gtk, _BackendGTK3
from matplotlib import transforms
class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3,
backend_agg.FigureCanvasAgg):
def __init__(self, figure):
backend_gtk3.FigureCanvasGTK3.__init__(self, figure)
self._bbox_queue = []
def _renderer_init(self):
pass
def _render_figure(self, width, height):
backend_agg.FigureCanvasAgg.draw(self)
def on_draw_event(self, widget, ctx):
"""GtkDrawable draw event, like expose_event in GTK 2.X.
"""
allocation = self.get_allocation()
w, h = allocation.width, allocation.height
if not len(self._bbox_queue):
self._render_figure(w, h)
Gtk.render_background(
self.get_style_context(), ctx,
allocation.x, allocation.y,
allocation.width, allocation.height)
bbox_queue = [transforms.Bbox([[0, 0], [w, h]])]
else:
bbox_queue = self._bbox_queue
ctx = backend_cairo._to_context(ctx)
for bbox in bbox_queue:
x = int(bbox.x0)
y = h - int(bbox.y1)
width = int(bbox.x1) - int(bbox.x0)
height = int(bbox.y1) - int(bbox.y0)
buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(
np.asarray(self.copy_from_bbox(bbox)))
image = cairo.ImageSurface.create_for_data(
buf.ravel().data, cairo.FORMAT_ARGB32, width, height)
ctx.set_source_surface(image, x, y)
ctx.paint()
if len(self._bbox_queue):
self._bbox_queue = []
return False
def blit(self, bbox=None):
# If bbox is None, blit the entire canvas to gtk. Otherwise
# blit only the area defined by the bbox.
if bbox is None:
bbox = self.figure.bbox
allocation = self.get_allocation()
w, h = allocation.width, allocation.height
x = int(bbox.x0)
y = h - int(bbox.y1)
width = int(bbox.x1) - int(bbox.x0)
height = int(bbox.y1) - int(bbox.y0)
self._bbox_queue.append(bbox)
self.queue_draw_area(x, y, width, height)
def print_png(self, filename, *args, **kwargs):
# Do this so we can save the resolution of figure in the PNG file
agg = self.switch_backends(backend_agg.FigureCanvasAgg)
return agg.print_png(filename, *args, **kwargs)
class FigureManagerGTK3Agg(backend_gtk3.FigureManagerGTK3):
pass
@_BackendGTK3.export
class _BackendGTK3Cairo(_BackendGTK3):
FigureCanvas = FigureCanvasGTK3Agg
FigureManager = FigureManagerGTK3Agg
@@ -1,46 +0,0 @@
from . import backend_cairo, backend_gtk3
from ._gtk3_compat import gi
from .backend_gtk3 import Gtk, _BackendGTK3
from matplotlib.backend_bases import cursors
class RendererGTK3Cairo(backend_cairo.RendererCairo):
def set_context(self, ctx):
self.gc.ctx = backend_cairo._to_context(ctx)
class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3,
backend_cairo.FigureCanvasCairo):
def _renderer_init(self):
"""Use cairo renderer."""
self._renderer = RendererGTK3Cairo(self.figure.dpi)
def _render_figure(self, width, height):
self._renderer.set_width_height(width, height)
self.figure.draw(self._renderer)
def on_draw_event(self, widget, ctx):
"""GtkDrawable draw event."""
toolbar = self.toolbar
# if toolbar:
# toolbar.set_cursor(cursors.WAIT)
self._renderer.set_context(ctx)
allocation = self.get_allocation()
Gtk.render_background(
self.get_style_context(), ctx,
allocation.x, allocation.y, allocation.width, allocation.height)
self._render_figure(allocation.width, allocation.height)
# if toolbar:
# toolbar.set_cursor(toolbar._lastCursor)
return False # finish event propagation?
class FigureManagerGTK3Cairo(backend_gtk3.FigureManagerGTK3):
pass
@_BackendGTK3.export
class _BackendGTK3Cairo(_BackendGTK3):
FigureCanvas = FigureCanvasGTK3Cairo
FigureManager = FigureManagerGTK3Cairo
@@ -1,203 +0,0 @@
import os
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
TimerBase)
from matplotlib.figure import Figure
from matplotlib import rcParams
from matplotlib.widgets import SubplotTool
import matplotlib
from matplotlib.backends import _macosx
from .backend_agg import FigureCanvasAgg
########################################################################
#
# The following functions and classes are for pylab and implement
# window/figure managers, etc...
#
########################################################################
class TimerMac(_macosx.Timer, TimerBase):
'''
Subclass of :class:`backend_bases.TimerBase` that uses CoreFoundation
run loops for timer events.
Attributes
----------
interval : int
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
Stores list of (func, args) 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.
'''
# completely implemented at the C-level (in _macosx.Timer)
class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasAgg):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...
Events such as button presses, mouse movements, and key presses
are handled in the C code and the base class methods
button_press_event, button_release_event, motion_notify_event,
key_press_event, and key_release_event are called from there.
Attributes
----------
figure : `matplotlib.figure.Figure`
A high-level Figure instance
"""
def __init__(self, figure):
FigureCanvasBase.__init__(self, figure)
width, height = self.get_width_height()
_macosx.FigureCanvas.__init__(self, width, height)
self._device_scale = 1.0
def _set_device_scale(self, value):
if self._device_scale != value:
self.figure.dpi = self.figure.dpi / self._device_scale * value
self._device_scale = value
def _draw(self):
renderer = self.get_renderer(cleared=self.figure.stale)
if self.figure.stale:
self.figure.draw(renderer)
return renderer
def draw(self):
self.invalidate()
self.flush_events()
def draw_idle(self, *args, **kwargs):
self.invalidate()
def blit(self, bbox=None):
self.invalidate()
def resize(self, width, height):
dpi = self.figure.dpi
width /= dpi
height /= dpi
self.figure.set_size_inches(width * self._device_scale,
height * self._device_scale,
forward=False)
FigureCanvasBase.resize_event(self)
self.draw_idle()
def new_timer(self, *args, **kwargs):
"""
Creates a new backend-specific subclass of `backend_bases.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
Sequence of (func, args, kwargs) where ``func(*args, **kwargs)``
will be executed by the timer every *interval*.
"""
return TimerMac(*args, **kwargs)
class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
"""
Wrap everything up into a window for the pylab interface
"""
def __init__(self, canvas, num):
FigureManagerBase.__init__(self, canvas, num)
title = "Figure %d" % num
_macosx.FigureManager.__init__(self, canvas, title)
if rcParams['toolbar'] == 'toolbar2':
self.toolbar = NavigationToolbar2Mac(canvas)
else:
self.toolbar = None
if self.toolbar is not None:
self.toolbar.update()
if matplotlib.is_interactive():
self.show()
self.canvas.draw_idle()
def close(self):
Gcf.destroy(self.num)
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
def __init__(self, canvas):
NavigationToolbar2.__init__(self, canvas)
def _init_toolbar(self):
basedir = os.path.join(rcParams['datapath'], "images")
_macosx.NavigationToolbar2.__init__(self, basedir)
def draw_rubberband(self, event, x0, y0, x1, y1):
self.canvas.set_rubberband(int(x0), int(y0), int(x1), int(y1))
def release(self, event):
self.canvas.remove_rubberband()
def set_cursor(self, cursor):
_macosx.set_cursor(cursor)
def save_figure(self, *args):
filename = _macosx.choose_save_file('Save the figure',
self.canvas.get_default_filename())
if filename is None: # Cancel
return
self.canvas.figure.savefig(filename)
def prepare_configure_subplots(self):
toolfig = Figure(figsize=(6,3))
canvas = FigureCanvasMac(toolfig)
toolfig.subplots_adjust(top=0.9)
tool = SubplotTool(self.canvas.figure, toolfig)
return canvas
def set_message(self, message):
_macosx.NavigationToolbar2.set_message(self, message.encode('utf-8'))
########################################################################
#
# Now just provide the standard names that backend.__init__ is expecting
#
########################################################################
@_Backend.export
class _BackendMac(_Backend):
required_interactive_framework = "macosx"
FigureCanvas = FigureCanvasMac
FigureManager = FigureManagerMac
@staticmethod
def trigger_manager_draw(manager):
# For performance reasons, we don't want to redraw the figure after
# each draw command. Instead, we mark the figure as invalid, so that it
# will be redrawn as soon as the event loop resumes via PyOS_InputHook.
# This function should be called after each draw event, even if
# matplotlib is not running interactively.
manager.canvas.invalidate()
@staticmethod
def mainloop():
_macosx.show()
@@ -1,150 +0,0 @@
import numpy as np
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib.tight_bbox import process_figure_for_rasterizing
class MixedModeRenderer(object):
"""
A helper class to implement a renderer that switches between
vector and raster drawing. An example may be a PDF writer, where
most things are drawn with PDF vector commands, but some very
complex objects, such as quad meshes, are rasterised and then
output as images.
"""
def __init__(self, figure, width, height, dpi, vector_renderer,
raster_renderer_class=None,
bbox_inches_restore=None):
"""
Parameters
----------
figure : `matplotlib.figure.Figure`
The figure instance.
width : scalar
The width of the canvas in logical units
height : scalar
The height of the canvas in logical units
dpi : scalar
The dpi of the canvas
vector_renderer : `matplotlib.backend_bases.RendererBase`
An instance of a subclass of
`~matplotlib.backend_bases.RendererBase` that will be used for the
vector drawing.
raster_renderer_class : `matplotlib.backend_bases.RendererBase`
The renderer class to use for the raster drawing. If not provided,
this will use the Agg backend (which is currently the only viable
option anyway.)
"""
if raster_renderer_class is None:
raster_renderer_class = RendererAgg
self._raster_renderer_class = raster_renderer_class
self._width = width
self._height = height
self.dpi = dpi
self._vector_renderer = vector_renderer
self._raster_renderer = None
self._rasterizing = 0
# A reference to the figure is needed as we need to change
# the figure dpi before and after the rasterization. Although
# this looks ugly, I couldn't find a better solution. -JJL
self.figure = figure
self._figdpi = figure.get_dpi()
self._bbox_inches_restore = bbox_inches_restore
self._set_current_renderer(vector_renderer)
_methods = """
close_group draw_image draw_markers draw_path
draw_path_collection draw_quad_mesh draw_tex draw_text
finalize flipy get_canvas_width_height get_image_magnification
get_texmanager get_text_width_height_descent new_gc open_group
option_image_nocomposite points_to_pixels strip_math
start_filter stop_filter draw_gouraud_triangle
draw_gouraud_triangles option_scale_image
_text2path _get_text_path_transform height width
""".split()
def _set_current_renderer(self, renderer):
self._renderer = renderer
for method in self._methods:
if hasattr(renderer, method):
setattr(self, method, getattr(renderer, method))
renderer.start_rasterizing = self.start_rasterizing
renderer.stop_rasterizing = self.stop_rasterizing
def start_rasterizing(self):
"""
Enter "raster" mode. All subsequent drawing commands (until
stop_rasterizing is called) will be drawn with the raster
backend.
If start_rasterizing is called multiple times before
stop_rasterizing is called, this method has no effect.
"""
# change the dpi of the figure temporarily.
self.figure.set_dpi(self.dpi)
if self._bbox_inches_restore: # when tight bbox is used
r = process_figure_for_rasterizing(self.figure,
self._bbox_inches_restore)
self._bbox_inches_restore = r
if self._rasterizing == 0:
self._raster_renderer = self._raster_renderer_class(
self._width*self.dpi, self._height*self.dpi, self.dpi)
self._set_current_renderer(self._raster_renderer)
self._rasterizing += 1
def stop_rasterizing(self):
"""
Exit "raster" mode. All of the drawing that was done since
the last start_rasterizing command will be copied to the
vector backend by calling draw_image.
If stop_rasterizing is called multiple times before
start_rasterizing is called, this method has no effect.
"""
self._rasterizing -= 1
if self._rasterizing == 0:
self._set_current_renderer(self._vector_renderer)
height = self._height * self.dpi
buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
l, b, w, h = bounds
if w > 0 and h > 0:
image = np.frombuffer(buffer, dtype=np.uint8)
image = image.reshape((h, w, 4))
image = image[::-1]
gc = self._renderer.new_gc()
# TODO: If the mixedmode resolution differs from the figure's
# dpi, the image must be scaled (dpi->_figdpi). Not all
# backends support this.
self._renderer.draw_image(
gc,
l * self._figdpi / self.dpi,
(height-b-h) * self._figdpi / self.dpi,
image)
self._raster_renderer = None
self._rasterizing = False
# restore the figure dpi.
self.figure.set_dpi(self._figdpi)
if self._bbox_inches_restore: # when tight bbox is used
r = process_figure_for_rasterizing(self.figure,
self._bbox_inches_restore,
self._figdpi)
self._bbox_inches_restore = r
@@ -1,265 +0,0 @@
"""Interactive figures in the IPython notebook"""
# Note: There is a notebook in
# lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify
# that changes made maintain expected behaviour.
from base64 import b64encode
import io
import json
import os
import pathlib
import uuid
from IPython.display import display, Javascript, HTML
try:
# Jupyter/IPython 4.x or later
from ipykernel.comm import Comm
except ImportError:
# Jupyter/IPython 3.x or earlier
from IPython.kernel.comm import Comm
from matplotlib import rcParams, is_interactive
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
_Backend, FigureCanvasBase, NavigationToolbar2)
from matplotlib.backends.backend_webagg_core import (
FigureCanvasWebAggCore, FigureManagerWebAgg, NavigationToolbar2WebAgg,
TimerTornado)
def connection_info():
"""
Return a string showing the figure and connection status for
the backend. This is intended as a diagnostic tool, and not for general
use.
"""
result = []
for manager in Gcf.get_all_fig_managers():
fig = manager.canvas.figure
result.append('{0} - {0}'.format((fig.get_label() or
"Figure {0}".format(manager.num)),
manager.web_sockets))
if not is_interactive():
result.append('Figures pending show: {0}'.format(len(Gcf._activeQue)))
return '\n'.join(result)
# Note: Version 3.2 and 4.x icons
# http://fontawesome.io/3.2.1/icons/
# http://fontawesome.io/
# the `fa fa-xxx` part targets font-awesome 4, (IPython 3.x)
# the icon-xxx targets font awesome 3.21 (IPython 2.x)
_FONT_AWESOME_CLASSES = {
'home': 'fa fa-home icon-home',
'back': 'fa fa-arrow-left icon-arrow-left',
'forward': 'fa fa-arrow-right icon-arrow-right',
'zoom_to_rect': 'fa fa-square-o icon-check-empty',
'move': 'fa fa-arrows icon-move',
'download': 'fa fa-floppy-o icon-save',
None: None
}
class NavigationIPy(NavigationToolbar2WebAgg):
# Use the standard toolbar items + download button
toolitems = [(text, tooltip_text,
_FONT_AWESOME_CLASSES[image_file], name_of_method)
for text, tooltip_text, image_file, name_of_method
in (NavigationToolbar2.toolitems +
(('Download', 'Download plot', 'download', 'download'),))
if image_file in _FONT_AWESOME_CLASSES]
class FigureManagerNbAgg(FigureManagerWebAgg):
ToolbarCls = NavigationIPy
def __init__(self, canvas, num):
self._shown = False
FigureManagerWebAgg.__init__(self, canvas, num)
def display_js(self):
# XXX How to do this just once? It has to deal with multiple
# browser instances using the same kernel (require.js - but the
# file isn't static?).
display(Javascript(FigureManagerNbAgg.get_javascript()))
def show(self):
if not self._shown:
self.display_js()
self._create_comm()
else:
self.canvas.draw_idle()
self._shown = True
def reshow(self):
"""
A special method to re-show the figure in the notebook.
"""
self._shown = False
self.show()
@property
def connected(self):
return bool(self.web_sockets)
@classmethod
def get_javascript(cls, stream=None):
if stream is None:
output = io.StringIO()
else:
output = stream
super().get_javascript(stream=output)
output.write((pathlib.Path(__file__).parent
/ "web_backend/js/nbagg_mpl.js")
.read_text(encoding="utf-8"))
if stream is None:
return output.getvalue()
def _create_comm(self):
comm = CommSocket(self)
self.add_web_socket(comm)
return comm
def destroy(self):
self._send_event('close')
# need to copy comms as callbacks will modify this list
for comm in list(self.web_sockets):
comm.on_close()
self.clearup_closed()
def clearup_closed(self):
"""Clear up any closed Comms."""
self.web_sockets = {socket for socket in self.web_sockets
if socket.is_open()}
if len(self.web_sockets) == 0:
self.canvas.close_event()
def remove_comm(self, comm_id):
self.web_sockets = {socket for socket in self.web_sockets
if not socket.comm.comm_id == comm_id}
class FigureCanvasNbAgg(FigureCanvasWebAggCore):
def new_timer(self, *args, **kwargs):
return TimerTornado(*args, **kwargs)
class CommSocket(object):
"""
Manages the Comm connection between IPython and the browser (client).
Comms are 2 way, with the CommSocket being able to publish a message
via the send_json method, and handle a message with on_message. On the
JS side figure.send_message and figure.ws.onmessage do the sending and
receiving respectively.
"""
def __init__(self, manager):
self.supports_binary = None
self.manager = manager
self.uuid = str(uuid.uuid4())
# Publish an output area with a unique ID. The javascript can then
# hook into this area.
display(HTML("<div id=%r></div>" % self.uuid))
try:
self.comm = Comm('matplotlib', data={'id': self.uuid})
except AttributeError:
raise RuntimeError('Unable to create an IPython notebook Comm '
'instance. Are you in the IPython notebook?')
self.comm.on_msg(self.on_message)
manager = self.manager
self._ext_close = False
def _on_close(close_message):
self._ext_close = True
manager.remove_comm(close_message['content']['comm_id'])
manager.clearup_closed()
self.comm.on_close(_on_close)
def is_open(self):
return not (self._ext_close or self.comm._closed)
def on_close(self):
# When the socket is closed, deregister the websocket with
# the FigureManager.
if self.is_open():
try:
self.comm.close()
except KeyError:
# apparently already cleaned it up?
pass
def send_json(self, content):
self.comm.send({'data': json.dumps(content)})
def send_binary(self, blob):
# The comm is ascii, so we always send the image in base64
# encoded data URL form.
data = b64encode(blob).decode('ascii')
data_uri = "data:image/png;base64,{0}".format(data)
self.comm.send({'data': data_uri})
def on_message(self, message):
# The 'supports_binary' message is relevant to the
# websocket itself. The other messages get passed along
# to matplotlib as-is.
# Every message has a "type" and a "figure_id".
message = json.loads(message['content']['data'])
if message['type'] == 'closing':
self.on_close()
self.manager.clearup_closed()
elif message['type'] == 'supports_binary':
self.supports_binary = message['value']
else:
self.manager.handle_json(message)
@_Backend.export
class _BackendNbAgg(_Backend):
FigureCanvas = FigureCanvasNbAgg
FigureManager = FigureManagerNbAgg
@staticmethod
def new_figure_manager_given_figure(num, figure):
canvas = FigureCanvasNbAgg(figure)
manager = FigureManagerNbAgg(canvas, num)
if is_interactive():
manager.show()
figure.canvas.draw_idle()
canvas.mpl_connect('close_event', lambda event: Gcf.destroy(num))
return manager
@staticmethod
def trigger_manager_draw(manager):
manager.show()
@staticmethod
def show(*args, **kwargs):
## TODO: something to do when keyword block==False ?
from matplotlib._pylab_helpers import Gcf
managers = Gcf.get_all_fig_managers()
if not managers:
return
interactive = is_interactive()
for manager in managers:
manager.show()
# plt.figure adds an event which puts the figure in focus
# in the activeQue. Disable this behaviour, as it results in
# figures being put as the active figure after they have been
# shown, even in non-interactive mode.
if hasattr(manager, '_cidgcf'):
manager.canvas.mpl_disconnect(manager._cidgcf)
if not interactive and manager in Gcf._activeQue:
Gcf._activeQue.remove(manager)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,10 +0,0 @@
from .backend_qt5 import (
backend_version, SPECIAL_KEYS, SUPER, ALT, CTRL, SHIFT, MODIFIER_KEYS,
cursord, _create_qApp, _BackendQT5, TimerQT, MainWindow, FigureManagerQT,
NavigationToolbar2QT, SubplotToolQt, error_msg_qt, exception_handler)
from .backend_qt5 import FigureCanvasQT as FigureCanvasQT5
@_BackendQT5.export
class _BackendQT4(_BackendQT5):
required_interactive_framework = "qt4"
@@ -1,11 +0,0 @@
"""
Render to qt from agg
"""
from .backend_qt5agg import (
_BackendQT5Agg, FigureCanvasQTAgg, FigureManagerQT, NavigationToolbar2QT)
@_BackendQT5Agg.export
class _BackendQT4Agg(_BackendQT5Agg):
required_interactive_framework = "qt4"
@@ -1,6 +0,0 @@
from .backend_qt5cairo import _BackendQT5Cairo
@_BackendQT5Cairo.export
class _BackendQT4Cairo(_BackendQT5Cairo):
required_interactive_framework = "qt4"
File diff suppressed because it is too large Load Diff
@@ -1,94 +0,0 @@
"""
Render to qt from agg
"""
import ctypes
from matplotlib.transforms import Bbox
from .. import cbook
from .backend_agg import FigureCanvasAgg
from .backend_qt5 import (
QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT,
NavigationToolbar2QT, backend_version)
from .qt_compat import QT_API
class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):
def __init__(self, figure):
# Must pass 'figure' as kwarg to Qt base class.
super().__init__(figure=figure)
def paintEvent(self, event):
"""Copy the image from the Agg canvas to the qt.drawable.
In Qt, all drawing should be done inside of here when a widget is
shown onscreen.
"""
if self._update_dpi():
# The dpi update triggered its own paintEvent.
return
self._draw_idle() # Only does something if a draw is pending.
# if the canvas does not have a renderer, then give up and wait for
# FigureCanvasAgg.draw(self) to be called
if not hasattr(self, 'renderer'):
return
painter = QtGui.QPainter(self)
if self._erase_before_paint:
painter.eraseRect(self.rect())
self._erase_before_paint = False
rect = event.rect()
left = rect.left()
top = rect.top()
width = rect.width()
height = rect.height()
# See documentation of QRect: bottom() and right() are off by 1, so use
# left() + width() and top() + height().
bbox = Bbox(
[[left, self.renderer.height - (top + height * self._dpi_ratio)],
[left + width * self._dpi_ratio, self.renderer.height - top]])
reg = self.copy_from_bbox(bbox)
buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(
memoryview(reg))
qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
QtGui.QImage.Format_ARGB32_Premultiplied)
if hasattr(qimage, 'setDevicePixelRatio'):
# Not available on Qt4 or some older Qt5.
qimage.setDevicePixelRatio(self._dpi_ratio)
origin = QtCore.QPoint(left, top)
painter.drawImage(origin / self._dpi_ratio, qimage)
# Adjust the buf reference count to work around a memory
# leak bug in QImage under PySide on Python 3.
if QT_API in ('PySide', 'PySide2'):
ctypes.c_long.from_address(id(buf)).value = 1
self._draw_rect_callback(painter)
painter.end()
def blit(self, bbox=None):
"""Blit the region in bbox.
"""
# If bbox is None, blit the entire canvas. Otherwise
# blit only the area defined by the bbox.
if bbox is None and self.figure:
bbox = self.figure.bbox
# repaint uses logical pixels, not physical pixels like the renderer.
l, b, w, h = [pt / self._dpi_ratio for pt in bbox.bounds]
t = b + h
self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h)
def print_figure(self, *args, **kwargs):
super().print_figure(*args, **kwargs)
self.draw()
@_BackendQT5.export
class _BackendQT5Agg(_BackendQT5):
FigureCanvas = FigureCanvasQTAgg
@@ -1,50 +0,0 @@
import ctypes
from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo
from .backend_qt5 import QtCore, QtGui, _BackendQT5, FigureCanvasQT
from .qt_compat import QT_API
class FigureCanvasQTCairo(FigureCanvasQT, FigureCanvasCairo):
def __init__(self, figure):
super().__init__(figure=figure)
self._renderer = RendererCairo(self.figure.dpi)
self._renderer.set_width_height(-1, -1) # Invalid values.
def draw(self):
if hasattr(self._renderer.gc, "ctx"):
self.figure.draw(self._renderer)
super().draw()
def paintEvent(self, event):
self._update_dpi()
dpi_ratio = self._dpi_ratio
width = dpi_ratio * self.width()
height = dpi_ratio * self.height()
if (width, height) != self._renderer.get_canvas_width_height():
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
self._renderer.set_ctx_from_surface(surface)
self._renderer.set_width_height(width, height)
self.figure.draw(self._renderer)
buf = self._renderer.gc.ctx.get_target().get_data()
qimage = QtGui.QImage(buf, width, height,
QtGui.QImage.Format_ARGB32_Premultiplied)
# Adjust the buf reference count to work around a memory leak bug in
# QImage under PySide on Python 3.
if QT_API == 'PySide':
ctypes.c_long.from_address(id(buf)).value = 1
if hasattr(qimage, 'setDevicePixelRatio'):
# Not available on Qt4 or some older Qt5.
qimage.setDevicePixelRatio(dpi_ratio)
painter = QtGui.QPainter(self)
if self._erase_before_paint:
painter.eraseRect(self.rect())
self._erase_before_paint = False
painter.drawImage(0, 0, qimage)
self._draw_rect_callback(painter)
painter.end()
@_BackendQT5.export
class _BackendQT5Cairo(_BackendQT5):
FigureCanvas = FigureCanvasQTCairo
File diff suppressed because it is too large Load Diff
@@ -1,266 +0,0 @@
"""
This is a fully functional do nothing backend to provide a template to
backend writers. It is fully functional in that you can select it as
a backend with
import matplotlib
matplotlib.use('Template')
and your matplotlib scripts will (should!) run without error, though
no output is produced. This provides a nice starting point for
backend writers because you can selectively implement methods
(draw_rectangle, draw_lines, etc...) and slowly see your figure come
to life w/o having to have a full blown implementation before getting
any results.
Copy this to backend_xxx.py and replace all instances of 'template'
with 'xxx'. Then implement the class methods and functions below, and
add 'xxx' to the switchyard in matplotlib/backends/__init__.py and
'xxx' to the backends list in the validate_backend methon in
matplotlib/__init__.py and you're off. You can use your backend with::
import matplotlib
matplotlib.use('xxx')
import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.show()
matplotlib also supports external backends, so you can place you can
use any module in your PYTHONPATH with the syntax::
import matplotlib
matplotlib.use('module://my_backend')
where my_backend.py is your module name. This syntax is also
recognized in the rc file and in the -d argument in pylab, e.g.,::
python simple_plot.py -dmodule://my_backend
If your backend implements support for saving figures (i.e. has a print_xyz()
method) you can register it as the default handler for a given file type
from matplotlib.backend_bases import register_backend
register_backend('xyz', 'my_backend', 'XYZ File Format')
...
plt.savefig("figure.xyz")
The files that are most relevant to backend_writers are
matplotlib/backends/backend_your_backend.py
matplotlib/backend_bases.py
matplotlib/backends/__init__.py
matplotlib/__init__.py
matplotlib/_pylab_helpers.py
Naming Conventions
* classes Upper or MixedUpperCase
* variables lower or lowerUpper
* functions lower or underscore_separated
"""
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
FigureCanvasBase, FigureManagerBase, GraphicsContextBase, RendererBase)
from matplotlib.figure import Figure
class RendererTemplate(RendererBase):
"""
The renderer handles drawing/rendering operations.
This is a minimal do-nothing class that can be used to get started when
writing a new backend. Refer to backend_bases.RendererBase for
documentation of the classes methods.
"""
def __init__(self, dpi):
self.dpi = dpi
def draw_path(self, gc, path, transform, rgbFace=None):
pass
# draw_markers is optional, and we get more correct relative
# timings by leaving it out. backend implementers concerned with
# performance will probably want to implement it
# def draw_markers(self, gc, marker_path, marker_trans, path, trans,
# rgbFace=None):
# pass
# draw_path_collection is optional, and we get more correct
# relative timings by leaving it out. backend implementers concerned with
# performance will probably want to implement it
# def draw_path_collection(self, gc, master_transform, paths,
# all_transforms, offsets, offsetTrans,
# facecolors, edgecolors, linewidths, linestyles,
# antialiaseds):
# pass
# draw_quad_mesh is optional, and we get more correct
# relative timings by leaving it out. backend implementers concerned with
# performance will probably want to implement it
# def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
# coordinates, offsets, offsetTrans, facecolors,
# antialiased, edgecolors):
# pass
def draw_image(self, gc, x, y, im):
pass
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
pass
def flipy(self):
return True
def get_canvas_width_height(self):
return 100, 100
def get_text_width_height_descent(self, s, prop, ismath):
return 1, 1, 1
def new_gc(self):
return GraphicsContextTemplate()
def points_to_pixels(self, points):
# if backend doesn't have dpi, e.g., postscript or svg
return points
# elif backend assumes a value for pixels_per_inch
#return points/72.0 * self.dpi.get() * pixels_per_inch/72.0
# else
#return points/72.0 * self.dpi.get()
class GraphicsContextTemplate(GraphicsContextBase):
"""
The graphics context provides the color, line styles, etc... See the cairo
and postscript backends for examples of mapping the graphics context
attributes (cap styles, join styles, line widths, colors) to a particular
backend. In cairo this is done by wrapping a cairo.Context object and
forwarding the appropriate calls to it using a dictionary mapping styles
to gdk constants. In Postscript, all the work is done by the renderer,
mapping line styles to postscript calls.
If it's more appropriate to do the mapping at the renderer level (as in
the postscript backend), you don't need to override any of the GC methods.
If it's more appropriate to wrap an instance (as in the cairo backend) and
do the mapping here, you'll need to override several of the setter
methods.
The base GraphicsContext stores colors as a RGB tuple on the unit
interval, e.g., (0.5, 0.0, 1.0). You may need to map this to colors
appropriate for your backend.
"""
########################################################################
#
# The following functions and classes are for pylab and implement
# window/figure managers, etc...
#
########################################################################
def draw_if_interactive():
"""
For image backends - is not required.
For GUI backends - this should be overridden if drawing should be done in
interactive python mode.
"""
def show(block=None):
"""
For image backends - is not required.
For GUI backends - show() is usually the last line of a pyplot script and
tells the backend that it is time to draw. In interactive mode, this
should do nothing.
"""
for manager in Gcf.get_all_fig_managers():
# do something to display the GUI
pass
def new_figure_manager(num, *args, FigureClass=Figure, **kwargs):
"""
Create a new figure manager instance
"""
# If a main-level app must be created, this (and
# new_figure_manager_given_figure) is the usual place to do it -- see
# backend_wx, backend_wxagg and backend_tkagg for examples. Not all GUIs
# require explicit instantiation of a main-level app (e.g., backend_gtk3)
# for pylab.
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)
def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasTemplate(figure)
manager = FigureManagerTemplate(canvas, num)
return manager
class FigureCanvasTemplate(FigureCanvasBase):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc.
Note: GUI templates will want to connect events for button presses,
mouse movements and key presses to functions that call the base
class methods button_press_event, button_release_event,
motion_notify_event, key_press_event, and key_release_event. See the
implementations of the interactive backends for examples.
Attributes
----------
figure : `matplotlib.figure.Figure`
A high-level Figure instance
"""
def draw(self):
"""
Draw the figure using the renderer
"""
renderer = RendererTemplate(self.figure.dpi)
self.figure.draw(renderer)
# You should provide a print_xxx function for every file format
# you can write.
# If the file type is not in the base set of filetypes,
# you should add it to the class-scope filetypes dictionary as follows:
filetypes = FigureCanvasBase.filetypes.copy()
filetypes['foo'] = 'My magic Foo format'
def print_foo(self, filename, *args, **kwargs):
"""
Write out format foo. The dpi, facecolor and edgecolor are restored
to their original values after this call, so you don't need to
save and restore them.
"""
pass
def get_default_filetype(self):
return 'foo'
class FigureManagerTemplate(FigureManagerBase):
"""
Wrap everything up into a window for the pylab interface
For non interactive backends, the base class does all the work
"""
pass
########################################################################
#
# Now just provide the standard names that backend.__init__ is expecting
#
########################################################################
FigureCanvas = FigureCanvasTemplate
FigureManager = FigureManagerTemplate
@@ -1,21 +0,0 @@
from . import _backend_tk
from .backend_agg import FigureCanvasAgg
from ._backend_tk import (
_BackendTk, FigureCanvasTk, FigureManagerTk, NavigationToolbar2Tk)
class FigureCanvasTkAgg(FigureCanvasAgg, FigureCanvasTk):
def draw(self):
super(FigureCanvasTkAgg, self).draw()
_backend_tk.blit(self._tkphoto, self.renderer._renderer, (0, 1, 2, 3))
self._master.update_idletasks()
def blit(self, bbox=None):
_backend_tk.blit(
self._tkphoto, self.renderer._renderer, (0, 1, 2, 3), bbox=bbox)
self._master.update_idletasks()
@_BackendTk.export
class _BackendTkAgg(_BackendTk):
FigureCanvas = FigureCanvasTkAgg
@@ -1,31 +0,0 @@
import sys
import numpy as np
from . import _backend_tk
from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo
from ._backend_tk import _BackendTk, FigureCanvasTk
class FigureCanvasTkCairo(FigureCanvasCairo, FigureCanvasTk):
def __init__(self, *args, **kwargs):
super(FigureCanvasTkCairo, self).__init__(*args, **kwargs)
self._renderer = RendererCairo(self.figure.dpi)
def draw(self):
width = int(self.figure.bbox.width)
height = int(self.figure.bbox.height)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
self._renderer.set_ctx_from_surface(surface)
self._renderer.set_width_height(width, height)
self.figure.draw(self._renderer)
buf = np.reshape(surface.get_data(), (height, width, 4))
_backend_tk.blit(
self._tkphoto, buf,
(2, 1, 0, 3) if sys.byteorder == "little" else (1, 2, 3, 0))
self._master.update_idletasks()
@_BackendTk.export
class _BackendTkCairo(_BackendTk):
FigureCanvas = FigureCanvasTkCairo
@@ -1,335 +0,0 @@
"""
Displays Agg images in the browser, with interactivity
"""
# The WebAgg backend is divided into two modules:
#
# - `backend_webagg_core.py` contains code necessary to embed a WebAgg
# plot inside of a web application, and communicate in an abstract
# way over a web socket.
#
# - `backend_webagg.py` contains a concrete implementation of a basic
# application, implemented with tornado.
from contextlib import contextmanager
import errno
from io import BytesIO
import json
import os
from pathlib import Path
import random
import sys
import signal
import socket
import threading
try:
import tornado
except ImportError:
raise RuntimeError("The WebAgg backend requires Tornado.")
import tornado.web
import tornado.ioloop
import tornado.websocket
from matplotlib import rcParams
from matplotlib.backend_bases import _Backend
from matplotlib._pylab_helpers import Gcf
from . import backend_webagg_core as core
from .backend_webagg_core import TimerTornado
class ServerThread(threading.Thread):
def run(self):
tornado.ioloop.IOLoop.instance().start()
webagg_server_thread = ServerThread()
class FigureCanvasWebAgg(core.FigureCanvasWebAggCore):
def show(self):
# show the figure window
global show # placates pyflakes: created by @_Backend.export below
show()
def new_timer(self, *args, **kwargs):
return TimerTornado(*args, **kwargs)
class WebAggApplication(tornado.web.Application):
initialized = False
started = False
class FavIcon(tornado.web.RequestHandler):
def get(self):
self.set_header('Content-Type', 'image/png')
image_path = Path(rcParams["datapath"], "images", "matplotlib.png")
self.write(image_path.read_bytes())
class SingleFigurePage(tornado.web.RequestHandler):
def __init__(self, application, request, *, url_prefix='', **kwargs):
self.url_prefix = url_prefix
super().__init__(application, request, **kwargs)
def get(self, fignum):
fignum = int(fignum)
manager = Gcf.get_fig_manager(fignum)
ws_uri = 'ws://{req.host}{prefix}/'.format(req=self.request,
prefix=self.url_prefix)
self.render(
"single_figure.html",
prefix=self.url_prefix,
ws_uri=ws_uri,
fig_id=fignum,
toolitems=core.NavigationToolbar2WebAgg.toolitems,
canvas=manager.canvas)
class AllFiguresPage(tornado.web.RequestHandler):
def __init__(self, application, request, *, url_prefix='', **kwargs):
self.url_prefix = url_prefix
super().__init__(application, request, **kwargs)
def get(self):
ws_uri = 'ws://{req.host}{prefix}/'.format(req=self.request,
prefix=self.url_prefix)
self.render(
"all_figures.html",
prefix=self.url_prefix,
ws_uri=ws_uri,
figures=sorted(Gcf.figs.items()),
toolitems=core.NavigationToolbar2WebAgg.toolitems)
class MplJs(tornado.web.RequestHandler):
def get(self):
self.set_header('Content-Type', 'application/javascript')
js_content = core.FigureManagerWebAgg.get_javascript()
self.write(js_content)
class Download(tornado.web.RequestHandler):
def get(self, fignum, fmt):
fignum = int(fignum)
manager = Gcf.get_fig_manager(fignum)
# TODO: Move this to a central location
mimetypes = {
'ps': 'application/postscript',
'eps': 'application/postscript',
'pdf': 'application/pdf',
'svg': 'image/svg+xml',
'png': 'image/png',
'jpeg': 'image/jpeg',
'tif': 'image/tiff',
'emf': 'application/emf'
}
self.set_header('Content-Type', mimetypes.get(fmt, 'binary'))
buff = BytesIO()
manager.canvas.figure.savefig(buff, format=fmt)
self.write(buff.getvalue())
class WebSocket(tornado.websocket.WebSocketHandler):
supports_binary = True
def open(self, fignum):
self.fignum = int(fignum)
self.manager = Gcf.get_fig_manager(self.fignum)
self.manager.add_web_socket(self)
if hasattr(self, 'set_nodelay'):
self.set_nodelay(True)
def on_close(self):
self.manager.remove_web_socket(self)
def on_message(self, message):
message = json.loads(message)
# The 'supports_binary' message is on a client-by-client
# basis. The others affect the (shared) canvas as a
# whole.
if message['type'] == 'supports_binary':
self.supports_binary = message['value']
else:
manager = Gcf.get_fig_manager(self.fignum)
# It is possible for a figure to be closed,
# but a stale figure UI is still sending messages
# from the browser.
if manager is not None:
manager.handle_json(message)
def send_json(self, content):
self.write_message(json.dumps(content))
def send_binary(self, blob):
if self.supports_binary:
self.write_message(blob, binary=True)
else:
data_uri = "data:image/png;base64,{0}".format(
blob.encode('base64').replace('\n', ''))
self.write_message(data_uri)
def __init__(self, url_prefix=''):
if url_prefix:
assert url_prefix[0] == '/' and url_prefix[-1] != '/', \
'url_prefix must start with a "/" and not end with one.'
super().__init__(
[
# Static files for the CSS and JS
(url_prefix + r'/_static/(.*)',
tornado.web.StaticFileHandler,
{'path': core.FigureManagerWebAgg.get_static_file_path()}),
# An MPL favicon
(url_prefix + r'/favicon.ico', self.FavIcon),
# The page that contains all of the pieces
(url_prefix + r'/([0-9]+)', self.SingleFigurePage,
{'url_prefix': url_prefix}),
# The page that contains all of the figures
(url_prefix + r'/?', self.AllFiguresPage,
{'url_prefix': url_prefix}),
(url_prefix + r'/js/mpl.js', self.MplJs),
# Sends images and events to the browser, and receives
# events from the browser
(url_prefix + r'/([0-9]+)/ws', self.WebSocket),
# Handles the downloading (i.e., saving) of static images
(url_prefix + r'/([0-9]+)/download.([a-z0-9.]+)',
self.Download),
],
template_path=core.FigureManagerWebAgg.get_static_file_path())
@classmethod
def initialize(cls, url_prefix='', port=None, address=None):
if cls.initialized:
return
# Create the class instance
app = cls(url_prefix=url_prefix)
cls.url_prefix = url_prefix
# This port selection algorithm is borrowed, more or less
# verbatim, from IPython.
def random_ports(port, n):
"""
Generate a list of n random ports near the given port.
The first 5 ports will be sequential, and the remaining n-5 will be
randomly selected in the range [port-2*n, port+2*n].
"""
for i in range(min(5, n)):
yield port + i
for i in range(n - 5):
yield port + random.randint(-2 * n, 2 * n)
if address is None:
cls.address = rcParams['webagg.address']
else:
cls.address = address
cls.port = rcParams['webagg.port']
for port in random_ports(cls.port, rcParams['webagg.port_retries']):
try:
app.listen(port, cls.address)
except socket.error as e:
if e.errno != errno.EADDRINUSE:
raise
else:
cls.port = port
break
else:
raise SystemExit(
"The webagg server could not be started because an available "
"port could not be found")
cls.initialized = True
@classmethod
def start(cls):
if cls.started:
return
"""
IOLoop.running() was removed as of Tornado 2.4; see for example
https://groups.google.com/forum/#!topic/python-tornado/QLMzkpQBGOY
Thus there is no correct way to check if the loop has already been
launched. We may end up with two concurrently running loops in that
unlucky case with all the expected consequences.
"""
ioloop = tornado.ioloop.IOLoop.instance()
def shutdown():
ioloop.stop()
print("Server is stopped")
sys.stdout.flush()
cls.started = False
@contextmanager
def catch_sigint():
old_handler = signal.signal(
signal.SIGINT,
lambda sig, frame: ioloop.add_callback_from_signal(shutdown))
try:
yield
finally:
signal.signal(signal.SIGINT, old_handler)
# Set the flag to True *before* blocking on ioloop.start()
cls.started = True
print("Press Ctrl+C to stop WebAgg server")
sys.stdout.flush()
with catch_sigint():
ioloop.start()
def ipython_inline_display(figure):
import tornado.template
WebAggApplication.initialize()
if not webagg_server_thread.is_alive():
webagg_server_thread.start()
fignum = figure.number
tpl = Path(core.FigureManagerWebAgg.get_static_file_path(),
"ipython_inline_figure.html").read_text()
t = tornado.template.Template(tpl)
return t.generate(
prefix=WebAggApplication.url_prefix,
fig_id=fignum,
toolitems=core.NavigationToolbar2WebAgg.toolitems,
canvas=figure.canvas,
port=WebAggApplication.port).decode('utf-8')
@_Backend.export
class _BackendWebAgg(_Backend):
FigureCanvas = FigureCanvasWebAgg
FigureManager = core.FigureManagerWebAgg
@staticmethod
def trigger_manager_draw(manager):
manager.canvas.draw_idle()
@staticmethod
def show():
WebAggApplication.initialize()
url = "http://127.0.0.1:{port}{prefix}".format(
port=WebAggApplication.port,
prefix=WebAggApplication.url_prefix)
if rcParams['webagg.open_in_browser']:
import webbrowser
webbrowser.open(url)
else:
print("To view figure, visit {0}".format(url))
WebAggApplication.start()
@@ -1,528 +0,0 @@
"""
Displays Agg images in the browser, with interactivity
"""
# The WebAgg backend is divided into two modules:
#
# - `backend_webagg_core.py` contains code necessary to embed a WebAgg
# plot inside of a web application, and communicate in an abstract
# way over a web socket.
#
# - `backend_webagg.py` contains a concrete implementation of a basic
# application, implemented with tornado.
import datetime
from io import StringIO
import json
import os
from pathlib import Path
import warnings
import numpy as np
import tornado
from matplotlib.backends import backend_agg
from matplotlib.backend_bases import _Backend
from matplotlib import backend_bases, _png
# http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
_SHIFT_LUT = {59: ':',
61: '+',
173: '_',
186: ':',
187: '+',
188: '<',
189: '_',
190: '>',
191: '?',
192: '~',
219: '{',
220: '|',
221: '}',
222: '"'}
_LUT = {8: 'backspace',
9: 'tab',
13: 'enter',
16: 'shift',
17: 'control',
18: 'alt',
19: 'pause',
20: 'caps',
27: 'escape',
32: ' ',
33: 'pageup',
34: 'pagedown',
35: 'end',
36: 'home',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
45: 'insert',
46: 'delete',
91: 'super',
92: 'super',
93: 'select',
106: '*',
107: '+',
109: '-',
110: '.',
111: '/',
144: 'num_lock',
145: 'scroll_lock',
186: ':',
187: '=',
188: ',',
189: '-',
190: '.',
191: '/',
192: '`',
219: '[',
220: '\\',
221: ']',
222: "'"}
def _handle_key(key):
"""Handle key codes"""
code = int(key[key.index('k') + 1:])
value = chr(code)
# letter keys
if 65 <= code <= 90:
if 'shift+' in key:
key = key.replace('shift+', '')
else:
value = value.lower()
# number keys
elif 48 <= code <= 57:
if 'shift+' in key:
value = ')!@#$%^&*('[int(value)]
key = key.replace('shift+', '')
# function keys
elif 112 <= code <= 123:
value = 'f%s' % (code - 111)
# number pad keys
elif 96 <= code <= 105:
value = '%s' % (code - 96)
# keys with shift alternatives
elif code in _SHIFT_LUT and 'shift+' in key:
key = key.replace('shift+', '')
value = _SHIFT_LUT[code]
elif code in _LUT:
value = _LUT[code]
key = key[:key.index('k')] + value
return key
class FigureCanvasWebAggCore(backend_agg.FigureCanvasAgg):
supports_blit = False
def __init__(self, *args, **kwargs):
backend_agg.FigureCanvasAgg.__init__(self, *args, **kwargs)
# Set to True when the renderer contains data that is newer
# than the PNG buffer.
self._png_is_old = True
# Set to True by the `refresh` message so that the next frame
# sent to the clients will be a full frame.
self._force_full = True
# Store the current image mode so that at any point, clients can
# request the information. This should be changed by calling
# self.set_image_mode(mode) so that the notification can be given
# to the connected clients.
self._current_image_mode = 'full'
# Store the DPI ratio of the browser. This is the scaling that
# occurs automatically for all images on a HiDPI display.
self._dpi_ratio = 1
def show(self):
# show the figure window
from matplotlib.pyplot import show
show()
def draw(self):
self._png_is_old = True
try:
super().draw()
finally:
self.manager.refresh_all() # Swap the frames.
def draw_idle(self):
self.send_event("draw")
def set_image_mode(self, mode):
"""
Set the image mode for any subsequent images which will be sent
to the clients. The modes may currently be either 'full' or 'diff'.
Note: diff images may not contain transparency, therefore upon
draw this mode may be changed if the resulting image has any
transparent component.
"""
if mode not in ['full', 'diff']:
raise ValueError('image mode must be either full or diff.')
if self._current_image_mode != mode:
self._current_image_mode = mode
self.handle_send_image_mode(None)
def get_diff_image(self):
if self._png_is_old:
renderer = self.get_renderer()
# The buffer is created as type uint32 so that entire
# pixels can be compared in one numpy call, rather than
# needing to compare each plane separately.
buff = (np.frombuffer(renderer.buffer_rgba(), dtype=np.uint32)
.reshape((renderer.height, renderer.width)))
# If any pixels have transparency, we need to force a full
# draw as we cannot overlay new on top of old.
pixels = buff.view(dtype=np.uint8).reshape(buff.shape + (4,))
if self._force_full or np.any(pixels[:, :, 3] != 255):
self.set_image_mode('full')
output = buff
else:
self.set_image_mode('diff')
last_buffer = (np.frombuffer(self._last_renderer.buffer_rgba(),
dtype=np.uint32)
.reshape((renderer.height, renderer.width)))
diff = buff != last_buffer
output = np.where(diff, buff, 0)
# TODO: We should write a new version of write_png that
# handles the differencing inline
buff = _png.write_png(
output.view(dtype=np.uint8).reshape(output.shape + (4,)),
None, compression=6, filter=_png.PNG_FILTER_NONE)
# Swap the renderer frames
self._renderer, self._last_renderer = (
self._last_renderer, renderer)
self._force_full = False
self._png_is_old = False
return buff
def get_renderer(self, cleared=None):
# Mirrors super.get_renderer, but caches the old one
# so that we can do things such as produce a diff image
# in get_diff_image
_, _, w, h = self.figure.bbox.bounds
w, h = int(w), int(h)
key = w, h, self.figure.dpi
try:
self._lastKey, self._renderer
except AttributeError:
need_new_renderer = True
else:
need_new_renderer = (self._lastKey != key)
if need_new_renderer:
self._renderer = backend_agg.RendererAgg(
w, h, self.figure.dpi)
self._last_renderer = backend_agg.RendererAgg(
w, h, self.figure.dpi)
self._lastKey = key
elif cleared:
self._renderer.clear()
return self._renderer
def handle_event(self, event):
e_type = event['type']
handler = getattr(self, 'handle_{0}'.format(e_type),
self.handle_unknown_event)
return handler(event)
def handle_unknown_event(self, event):
warnings.warn('Unhandled message type {0}. {1}'.format(
event['type'], event), stacklevel=2)
def handle_ack(self, event):
# Network latency tends to decrease if traffic is flowing
# in both directions. Therefore, the browser sends back
# an "ack" message after each image frame is received.
# This could also be used as a simple sanity check in the
# future, but for now the performance increase is enough
# to justify it, even if the server does nothing with it.
pass
def handle_draw(self, event):
self.draw()
def _handle_mouse(self, event):
x = event['x']
y = event['y']
y = self.get_renderer().height - y
# Javascript button numbers and matplotlib button numbers are
# off by 1
button = event['button'] + 1
# The right mouse button pops up a context menu, which
# doesn't work very well, so use the middle mouse button
# instead. It doesn't seem that it's possible to disable
# the context menu in recent versions of Chrome. If this
# is resolved, please also adjust the docstring in MouseEvent.
if button == 2:
button = 3
e_type = event['type']
guiEvent = event.get('guiEvent', None)
if e_type == 'button_press':
self.button_press_event(x, y, button, guiEvent=guiEvent)
elif e_type == 'button_release':
self.button_release_event(x, y, button, guiEvent=guiEvent)
elif e_type == 'motion_notify':
self.motion_notify_event(x, y, guiEvent=guiEvent)
elif e_type == 'figure_enter':
self.enter_notify_event(xy=(x, y), guiEvent=guiEvent)
elif e_type == 'figure_leave':
self.leave_notify_event()
elif e_type == 'scroll':
self.scroll_event(x, y, event['step'], guiEvent=guiEvent)
handle_button_press = handle_button_release = handle_motion_notify = \
handle_figure_enter = handle_figure_leave = handle_scroll = \
_handle_mouse
def _handle_key(self, event):
key = _handle_key(event['key'])
e_type = event['type']
guiEvent = event.get('guiEvent', None)
if e_type == 'key_press':
self.key_press_event(key, guiEvent=guiEvent)
elif e_type == 'key_release':
self.key_release_event(key, guiEvent=guiEvent)
handle_key_press = handle_key_release = _handle_key
def handle_toolbar_button(self, event):
# TODO: Be more suspicious of the input
getattr(self.toolbar, event['name'])()
def handle_refresh(self, event):
figure_label = self.figure.get_label()
if not figure_label:
figure_label = "Figure {0}".format(self.manager.num)
self.send_event('figure_label', label=figure_label)
self._force_full = True
self.draw_idle()
def handle_resize(self, event):
x, y = event.get('width', 800), event.get('height', 800)
x, y = int(x) * self._dpi_ratio, int(y) * self._dpi_ratio
fig = self.figure
# An attempt at approximating the figure size in pixels.
fig.set_size_inches(x / fig.dpi, y / fig.dpi, forward=False)
_, _, w, h = self.figure.bbox.bounds
# Acknowledge the resize, and force the viewer to update the
# canvas size to the figure's new size (which is hopefully
# identical or within a pixel or so).
self._png_is_old = True
self.manager.resize(w, h)
self.resize_event()
def handle_send_image_mode(self, event):
# The client requests notification of what the current image mode is.
self.send_event('image_mode', mode=self._current_image_mode)
def handle_set_dpi_ratio(self, event):
dpi_ratio = event.get('dpi_ratio', 1)
if dpi_ratio != self._dpi_ratio:
# We don't want to scale up the figure dpi more than once.
if not hasattr(self.figure, '_original_dpi'):
self.figure._original_dpi = self.figure.dpi
self.figure.dpi = dpi_ratio * self.figure._original_dpi
self._dpi_ratio = dpi_ratio
self._force_full = True
self.draw_idle()
def send_event(self, event_type, **kwargs):
self.manager._send_event(event_type, **kwargs)
_JQUERY_ICON_CLASSES = {
'home': 'ui-icon ui-icon-home',
'back': 'ui-icon ui-icon-circle-arrow-w',
'forward': 'ui-icon ui-icon-circle-arrow-e',
'zoom_to_rect': 'ui-icon ui-icon-search',
'move': 'ui-icon ui-icon-arrow-4',
'download': 'ui-icon ui-icon-disk',
None: None,
}
class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2):
# Use the standard toolbar items + download button
toolitems = [(text, tooltip_text, _JQUERY_ICON_CLASSES[image_file],
name_of_method)
for text, tooltip_text, image_file, name_of_method
in (backend_bases.NavigationToolbar2.toolitems +
(('Download', 'Download plot', 'download', 'download'),))
if image_file in _JQUERY_ICON_CLASSES]
def _init_toolbar(self):
self.message = ''
self.cursor = 0
def set_message(self, message):
if message != self.message:
self.canvas.send_event("message", message=message)
self.message = message
def set_cursor(self, cursor):
if cursor != self.cursor:
self.canvas.send_event("cursor", cursor=cursor)
self.cursor = cursor
def draw_rubberband(self, event, x0, y0, x1, y1):
self.canvas.send_event(
"rubberband", x0=x0, y0=y0, x1=x1, y1=y1)
def release_zoom(self, event):
backend_bases.NavigationToolbar2.release_zoom(self, event)
self.canvas.send_event(
"rubberband", x0=-1, y0=-1, x1=-1, y1=-1)
def save_figure(self, *args):
"""Save the current figure"""
self.canvas.send_event('save')
class FigureManagerWebAgg(backend_bases.FigureManagerBase):
ToolbarCls = NavigationToolbar2WebAgg
def __init__(self, canvas, num):
backend_bases.FigureManagerBase.__init__(self, canvas, num)
self.web_sockets = set()
self.toolbar = self._get_toolbar(canvas)
def show(self):
pass
def _get_toolbar(self, canvas):
toolbar = self.ToolbarCls(canvas)
return toolbar
def resize(self, w, h):
self._send_event(
'resize',
size=(w / self.canvas._dpi_ratio, h / self.canvas._dpi_ratio))
def set_window_title(self, title):
self._send_event('figure_label', label=title)
# The following methods are specific to FigureManagerWebAgg
def add_web_socket(self, web_socket):
assert hasattr(web_socket, 'send_binary')
assert hasattr(web_socket, 'send_json')
self.web_sockets.add(web_socket)
_, _, w, h = self.canvas.figure.bbox.bounds
self.resize(w, h)
self._send_event('refresh')
def remove_web_socket(self, web_socket):
self.web_sockets.remove(web_socket)
def handle_json(self, content):
self.canvas.handle_event(content)
def refresh_all(self):
if self.web_sockets:
diff = self.canvas.get_diff_image()
if diff is not None:
for s in self.web_sockets:
s.send_binary(diff)
@classmethod
def get_javascript(cls, stream=None):
if stream is None:
output = StringIO()
else:
output = stream
output.write((Path(__file__).parent / "web_backend/js/mpl.js")
.read_text(encoding="utf-8"))
toolitems = []
for name, tooltip, image, method in cls.ToolbarCls.toolitems:
if name is None:
toolitems.append(['', '', '', ''])
else:
toolitems.append([name, tooltip, image, method])
output.write("mpl.toolbar_items = {0};\n\n".format(
json.dumps(toolitems)))
extensions = []
for filetype, ext in sorted(FigureCanvasWebAggCore.
get_supported_filetypes_grouped().
items()):
if not ext[0] == 'pgf': # pgf does not support BytesIO
extensions.append(ext[0])
output.write("mpl.extensions = {0};\n\n".format(
json.dumps(extensions)))
output.write("mpl.default_extension = {0};".format(
json.dumps(FigureCanvasWebAggCore.get_default_filetype())))
if stream is None:
return output.getvalue()
@classmethod
def get_static_file_path(cls):
return os.path.join(os.path.dirname(__file__), 'web_backend')
def _send_event(self, event_type, **kwargs):
payload = {'type': event_type, **kwargs}
for s in self.web_sockets:
s.send_json(payload)
class TimerTornado(backend_bases.TimerBase):
def _timer_start(self):
self._timer_stop()
if self._single:
ioloop = tornado.ioloop.IOLoop.instance()
self._timer = ioloop.add_timeout(
datetime.timedelta(milliseconds=self.interval),
self._on_timer)
else:
self._timer = tornado.ioloop.PeriodicCallback(
self._on_timer,
self.interval)
self._timer.start()
def _timer_stop(self):
if self._timer is None:
return
elif self._single:
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.remove_timeout(self._timer)
else:
self._timer.stop()
self._timer = None
def _timer_set_interval(self):
# Only stop and restart it if the timer has already been started
if self._timer is not None:
self._timer_stop()
self._timer_start()
@_Backend.export
class _BackendWebAggCoreAgg(_Backend):
FigureCanvas = FigureCanvasWebAggCore
FigureManager = FigureManagerWebAgg
File diff suppressed because it is too large Load Diff
@@ -1,134 +0,0 @@
import wx
from .backend_agg import FigureCanvasAgg
from .backend_wx import (
_BackendWx, _FigureCanvasWxBase, FigureFrameWx,
NavigationToolbar2Wx as NavigationToolbar2WxAgg)
class FigureFrameWxAgg(FigureFrameWx):
def get_canvas(self, fig):
return FigureCanvasWxAgg(self, -1, fig)
class FigureCanvasWxAgg(FigureCanvasAgg, _FigureCanvasWxBase):
"""
The FigureCanvas contains the figure and does event handling.
In the wxPython backend, it is derived from wxPanel, and (usually)
lives inside a frame instantiated by a FigureManagerWx. The parent
window probably implements a wxSizer to control the displayed
control size - but we give a hint as to our preferred minimum
size.
"""
def draw(self, drawDC=None):
"""
Render the figure using agg.
"""
FigureCanvasAgg.draw(self)
self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
self._isDrawn = True
self.gui_repaint(drawDC=drawDC, origin='WXAgg')
def blit(self, bbox=None):
"""
Transfer the region of the agg buffer defined by bbox to the display.
If bbox is None, the entire buffer is transferred.
"""
if bbox is None:
self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
self.gui_repaint()
return
l, b, w, h = bbox.bounds
r = l + w
t = b + h
x = int(l)
y = int(self.bitmap.GetHeight() - t)
srcBmp = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
srcDC = wx.MemoryDC()
srcDC.SelectObject(srcBmp)
destDC = wx.MemoryDC()
destDC.SelectObject(self.bitmap)
destDC.Blit(x, y, int(w), int(h), srcDC, x, y)
destDC.SelectObject(wx.NullBitmap)
srcDC.SelectObject(wx.NullBitmap)
self.gui_repaint()
filetypes = FigureCanvasAgg.filetypes
# agg/wxPython image conversion functions (wxPython >= 2.8)
def _convert_agg_to_wx_image(agg, bbox):
"""
Convert the region of the agg buffer bounded by bbox to a wx.Image. If
bbox is None, the entire buffer is converted.
Note: agg must be a backend_agg.RendererAgg instance.
"""
if bbox is None:
# agg => rgb -> image
image = wx.Image(int(agg.width), int(agg.height))
image.SetData(agg.tostring_rgb())
return image
else:
# agg => rgba buffer -> bitmap => clipped bitmap => image
return wx.ImageFromBitmap(_WX28_clipped_agg_as_bitmap(agg, bbox))
def _convert_agg_to_wx_bitmap(agg, bbox):
"""
Convert the region of the agg buffer bounded by bbox to a wx.Bitmap. If
bbox is None, the entire buffer is converted.
Note: agg must be a backend_agg.RendererAgg instance.
"""
if bbox is None:
# agg => rgba buffer -> bitmap
return wx.Bitmap.FromBufferRGBA(int(agg.width), int(agg.height),
agg.buffer_rgba())
else:
# agg => rgba buffer -> bitmap => clipped bitmap
return _WX28_clipped_agg_as_bitmap(agg, bbox)
def _WX28_clipped_agg_as_bitmap(agg, bbox):
"""
Convert the region of a the agg buffer bounded by bbox to a wx.Bitmap.
Note: agg must be a backend_agg.RendererAgg instance.
"""
l, b, width, height = bbox.bounds
r = l + width
t = b + height
srcBmp = wx.Bitmap.FromBufferRGBA(int(agg.width), int(agg.height),
agg.buffer_rgba())
srcDC = wx.MemoryDC()
srcDC.SelectObject(srcBmp)
destBmp = wx.Bitmap(int(width), int(height))
destDC = wx.MemoryDC()
destDC.SelectObject(destBmp)
x = int(l)
y = int(int(agg.height) - t)
destDC.Blit(0, 0, int(width), int(height), srcDC, x, y)
srcDC.SelectObject(wx.NullBitmap)
destDC.SelectObject(wx.NullBitmap)
return destBmp
@_BackendWx.export
class _BackendWxAgg(_BackendWx):
FigureCanvas = FigureCanvasWxAgg
_frame_class = FigureFrameWxAgg
@@ -1,48 +0,0 @@
import wx
from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo
from .backend_wx import (
_BackendWx, _FigureCanvasWxBase, FigureFrameWx,
NavigationToolbar2Wx as NavigationToolbar2WxCairo)
import wx.lib.wxcairo as wxcairo
class FigureFrameWxCairo(FigureFrameWx):
def get_canvas(self, fig):
return FigureCanvasWxCairo(self, -1, fig)
class FigureCanvasWxCairo(_FigureCanvasWxBase, FigureCanvasCairo):
"""
The FigureCanvas contains the figure and does event handling.
In the wxPython backend, it is derived from wxPanel, and (usually) lives
inside a frame instantiated by a FigureManagerWx. The parent window
probably implements a wxSizer to control the displayed control size - but
we give a hint as to our preferred minimum size.
"""
def __init__(self, parent, id, figure):
# _FigureCanvasWxBase should be fixed to have the same signature as
# every other FigureCanvas and use cooperative inheritance, but in the
# meantime the following will make do.
_FigureCanvasWxBase.__init__(self, parent, id, figure)
FigureCanvasCairo.__init__(self, figure)
self._renderer = RendererCairo(self.figure.dpi)
def draw(self, drawDC=None):
width = int(self.figure.bbox.width)
height = int(self.figure.bbox.height)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
self._renderer.set_ctx_from_surface(surface)
self._renderer.set_width_height(width, height)
self.figure.draw(self._renderer)
self.bitmap = wxcairo.BitmapFromImageSurface(surface)
self._isDrawn = True
self.gui_repaint(drawDC=drawDC, origin='WXCairo')
@_BackendWx.export
class _BackendWxCairo(_BackendWx):
FigureCanvas = FigureCanvasWxCairo
_frame_class = FigureFrameWxCairo
@@ -1,173 +0,0 @@
"""
Qt binding and backend selector.
The selection logic is as follows:
- if any of PyQt5, PySide2, PyQt4 or PySide have already been imported
(checked in that order), use it;
- otherwise, if the QT_API environment variable (used by Enthought) is
set, use it to determine which binding to use (but do not change the
backend based on it; i.e. if the Qt4Agg backend is requested but QT_API
is set to "pyqt5", then actually use Qt4 with the binding specified by
``rcParams["backend.qt4"]``;
- otherwise, use whatever the rcParams indicate.
"""
from distutils.version import LooseVersion
import os
import sys
from matplotlib import rcParams
QT_API_PYQT5 = "PyQt5"
QT_API_PYSIDE2 = "PySide2"
QT_API_PYQTv2 = "PyQt4v2"
QT_API_PYSIDE = "PySide"
QT_API_PYQT = "PyQt4" # Use the old sip v1 API (Py3 defaults to v2).
QT_API_ENV = os.environ.get("QT_API")
# Mapping of QT_API_ENV to requested binding. ETS does not support PyQt4v1.
# (https://github.com/enthought/pyface/blob/master/pyface/qt/__init__.py)
_ETS = {"pyqt5": QT_API_PYQT5, "pyside2": QT_API_PYSIDE2,
"pyqt": QT_API_PYQTv2, "pyside": QT_API_PYSIDE,
None: None}
# First, check if anything is already imported.
if "PyQt5" in sys.modules:
QT_API = QT_API_PYQT5
dict.__setitem__(rcParams, "backend.qt5", QT_API)
elif "PySide2" in sys.modules:
QT_API = QT_API_PYSIDE2
dict.__setitem__(rcParams, "backend.qt5", QT_API)
elif "PyQt4" in sys.modules:
QT_API = QT_API_PYQTv2
dict.__setitem__(rcParams, "backend.qt4", QT_API)
elif "PySide" in sys.modules:
QT_API = QT_API_PYSIDE
dict.__setitem__(rcParams, "backend.qt4", QT_API)
# Otherwise, check the QT_API environment variable (from Enthought). This can
# only override the binding, not the backend (in other words, we check that the
# requested backend actually matches).
elif rcParams["backend"] in ["Qt5Agg", "Qt5Cairo"]:
if QT_API_ENV == "pyqt5":
dict.__setitem__(rcParams, "backend.qt5", QT_API_PYQT5)
elif QT_API_ENV == "pyside2":
dict.__setitem__(rcParams, "backend.qt5", QT_API_PYSIDE2)
QT_API = dict.__getitem__(rcParams, "backend.qt5")
elif rcParams["backend"] in ["Qt4Agg", "Qt4Cairo"]:
if QT_API_ENV == "pyqt4":
dict.__setitem__(rcParams, "backend.qt4", QT_API_PYQTv2)
elif QT_API_ENV == "pyside":
dict.__setitem__(rcParams, "backend.qt4", QT_API_PYSIDE)
QT_API = dict.__getitem__(rcParams, "backend.qt4")
# A non-Qt backend was selected but we still got there (possible, e.g., when
# fully manually embedding Matplotlib in a Qt app without using pyplot).
else:
try:
QT_API = _ETS[QT_API_ENV]
except KeyError:
raise RuntimeError(
"The environment variable QT_API has the unrecognized value {!r};"
"valid values are 'pyqt5', 'pyside2', 'pyqt', and 'pyside'")
def _setup_pyqt5():
global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, _getSaveFileName
if QT_API == QT_API_PYQT5:
from PyQt5 import QtCore, QtGui, QtWidgets
__version__ = QtCore.PYQT_VERSION_STR
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
QtCore.Property = QtCore.pyqtProperty
elif QT_API == QT_API_PYSIDE2:
from PySide2 import QtCore, QtGui, QtWidgets, __version__
else:
raise ValueError("Unexpected value for the 'backend.qt5' rcparam")
_getSaveFileName = QtWidgets.QFileDialog.getSaveFileName
def is_pyqt5():
return True
def _setup_pyqt4():
global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, _getSaveFileName
def _setup_pyqt4_internal(api):
global QtCore, QtGui, QtWidgets, \
__version__, is_pyqt5, _getSaveFileName
# List of incompatible APIs:
# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
_sip_apis = ["QDate", "QDateTime", "QString", "QTextStream", "QTime",
"QUrl", "QVariant"]
try:
import sip
except ImportError:
pass
else:
for _sip_api in _sip_apis:
try:
sip.setapi(_sip_api, api)
except ValueError:
pass
from PyQt4 import QtCore, QtGui
__version__ = QtCore.PYQT_VERSION_STR
# PyQt 4.6 introduced getSaveFileNameAndFilter:
# https://riverbankcomputing.com/news/pyqt-46
if __version__ < LooseVersion("4.6"):
raise ImportError("PyQt<4.6 is not supported")
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
QtCore.Property = QtCore.pyqtProperty
_getSaveFileName = QtGui.QFileDialog.getSaveFileNameAndFilter
if QT_API == QT_API_PYQTv2:
_setup_pyqt4_internal(api=2)
elif QT_API == QT_API_PYSIDE:
from PySide import QtCore, QtGui, __version__, __version_info__
# PySide 1.0.3 fixed the following:
# https://srinikom.github.io/pyside-bz-archive/809.html
if __version_info__ < (1, 0, 3):
raise ImportError("PySide<1.0.3 is not supported")
_getSaveFileName = QtGui.QFileDialog.getSaveFileName
elif QT_API == QT_API_PYQT:
_setup_pyqt4_internal(api=1)
else:
raise ValueError("Unexpected value for the 'backend.qt4' rcparam")
QtWidgets = QtGui
def is_pyqt5():
return False
if QT_API in [QT_API_PYQT5, QT_API_PYSIDE2]:
_setup_pyqt5()
elif QT_API in [QT_API_PYQTv2, QT_API_PYSIDE, QT_API_PYQT]:
_setup_pyqt4()
elif QT_API is None:
if rcParams["backend"] == "Qt4Agg":
_candidates = [(_setup_pyqt4, QT_API_PYQTv2),
(_setup_pyqt4, QT_API_PYSIDE),
(_setup_pyqt4, QT_API_PYQT),
(_setup_pyqt5, QT_API_PYQT5),
(_setup_pyqt5, QT_API_PYSIDE2)]
else:
_candidates = [(_setup_pyqt5, QT_API_PYQT5),
(_setup_pyqt5, QT_API_PYSIDE2),
(_setup_pyqt4, QT_API_PYQTv2),
(_setup_pyqt4, QT_API_PYSIDE),
(_setup_pyqt4, QT_API_PYQT)]
for _setup, QT_API in _candidates:
try:
_setup()
except ImportError:
continue
break
else:
raise ImportError("Failed to import any qt binding")
else: # We should not get there.
raise AssertionError("Unexpected QT_API: {}".format(QT_API))
# These globals are only defined for backcompatibilty purposes.
ETS = dict(pyqt=(QT_API_PYQTv2, 4), pyside=(QT_API_PYSIDE, 4),
pyqt5=(QT_API_PYQT5, 5), pyside2=(QT_API_PYSIDE2, 5))
QT_RC_MAJOR_VERSION = 5 if is_pyqt5() else 4
@@ -1,257 +0,0 @@
# Copyright © 2009 Pierre Raybaut
# Licensed under the terms of the MIT License
# see the mpl licenses directory for a copy of the license
"""Module that provides a GUI-based editor for matplotlib's figure options."""
import os.path
import re
import matplotlib
from matplotlib import cm, colors as mcolors, markers, image as mimage
import matplotlib.backends.qt_editor.formlayout as formlayout
from matplotlib.backends.qt_compat import QtGui
def get_icon(name):
basedir = os.path.join(matplotlib.rcParams['datapath'], 'images')
return QtGui.QIcon(os.path.join(basedir, name))
LINESTYLES = {'-': 'Solid',
'--': 'Dashed',
'-.': 'DashDot',
':': 'Dotted',
'None': 'None',
}
DRAWSTYLES = {
'default': 'Default',
'steps-pre': 'Steps (Pre)', 'steps': 'Steps (Pre)',
'steps-mid': 'Steps (Mid)',
'steps-post': 'Steps (Post)'}
MARKERS = markers.MarkerStyle.markers
def figure_edit(axes, parent=None):
"""Edit matplotlib figure options"""
sep = (None, None) # separator
# Get / General
# Cast to builtin floats as they have nicer reprs.
xmin, xmax = map(float, axes.get_xlim())
ymin, ymax = map(float, axes.get_ylim())
general = [('Title', axes.get_title()),
sep,
(None, "<b>X-Axis</b>"),
('Left', xmin), ('Right', xmax),
('Label', axes.get_xlabel()),
('Scale', [axes.get_xscale(), 'linear', 'log', 'logit']),
sep,
(None, "<b>Y-Axis</b>"),
('Bottom', ymin), ('Top', ymax),
('Label', axes.get_ylabel()),
('Scale', [axes.get_yscale(), 'linear', 'log', 'logit']),
sep,
('(Re-)Generate automatic legend', False),
]
# Save the unit data
xconverter = axes.xaxis.converter
yconverter = axes.yaxis.converter
xunits = axes.xaxis.get_units()
yunits = axes.yaxis.get_units()
# Sorting for default labels (_lineXXX, _imageXXX).
def cmp_key(label):
match = re.match(r"(_line|_image)(\d+)", label)
if match:
return match.group(1), int(match.group(2))
else:
return label, 0
# Get / Curves
linedict = {}
for line in axes.get_lines():
label = line.get_label()
if label == '_nolegend_':
continue
linedict[label] = line
curves = []
def prepare_data(d, init):
"""Prepare entry for FormLayout.
`d` is a mapping of shorthands to style names (a single style may
have multiple shorthands, in particular the shorthands `None`,
`"None"`, `"none"` and `""` are synonyms); `init` is one shorthand
of the initial style.
This function returns an list suitable for initializing a
FormLayout combobox, namely `[initial_name, (shorthand,
style_name), (shorthand, style_name), ...]`.
"""
if init not in d:
d = {**d, init: str(init)}
# Drop duplicate shorthands from dict (by overwriting them during
# the dict comprehension).
name2short = {name: short for short, name in d.items()}
# Convert back to {shorthand: name}.
short2name = {short: name for name, short in name2short.items()}
# Find the kept shorthand for the style specified by init.
canonical_init = name2short[d[init]]
# Sort by representation and prepend the initial value.
return ([canonical_init] +
sorted(short2name.items(),
key=lambda short_and_name: short_and_name[1]))
curvelabels = sorted(linedict, key=cmp_key)
for label in curvelabels:
line = linedict[label]
color = mcolors.to_hex(
mcolors.to_rgba(line.get_color(), line.get_alpha()),
keep_alpha=True)
ec = mcolors.to_hex(
mcolors.to_rgba(line.get_markeredgecolor(), line.get_alpha()),
keep_alpha=True)
fc = mcolors.to_hex(
mcolors.to_rgba(line.get_markerfacecolor(), line.get_alpha()),
keep_alpha=True)
curvedata = [
('Label', label),
sep,
(None, '<b>Line</b>'),
('Line style', prepare_data(LINESTYLES, line.get_linestyle())),
('Draw style', prepare_data(DRAWSTYLES, line.get_drawstyle())),
('Width', line.get_linewidth()),
('Color (RGBA)', color),
sep,
(None, '<b>Marker</b>'),
('Style', prepare_data(MARKERS, line.get_marker())),
('Size', line.get_markersize()),
('Face color (RGBA)', fc),
('Edge color (RGBA)', ec)]
curves.append([curvedata, label, ""])
# Is there a curve displayed?
has_curve = bool(curves)
# Get / Images
imagedict = {}
for image in axes.get_images():
label = image.get_label()
if label == '_nolegend_':
continue
imagedict[label] = image
imagelabels = sorted(imagedict, key=cmp_key)
images = []
cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())]
for label in imagelabels:
image = imagedict[label]
cmap = image.get_cmap()
if cmap not in cm.cmap_d.values():
cmaps = [(cmap, cmap.name)] + cmaps
low, high = image.get_clim()
imagedata = [
('Label', label),
('Colormap', [cmap.name] + cmaps),
('Min. value', low),
('Max. value', high),
('Interpolation',
[image.get_interpolation()]
+ [(name, name) for name in sorted(mimage.interpolations_names)])]
images.append([imagedata, label, ""])
# Is there an image displayed?
has_image = bool(images)
datalist = [(general, "Axes", "")]
if curves:
datalist.append((curves, "Curves", ""))
if images:
datalist.append((images, "Images", ""))
def apply_callback(data):
"""This function will be called to apply changes"""
orig_xlim = axes.get_xlim()
orig_ylim = axes.get_ylim()
general = data.pop(0)
curves = data.pop(0) if has_curve else []
images = data.pop(0) if has_image else []
if data:
raise ValueError("Unexpected field")
# Set / General
(title, xmin, xmax, xlabel, xscale, ymin, ymax, ylabel, yscale,
generate_legend) = general
if axes.get_xscale() != xscale:
axes.set_xscale(xscale)
if axes.get_yscale() != yscale:
axes.set_yscale(yscale)
axes.set_title(title)
axes.set_xlim(xmin, xmax)
axes.set_xlabel(xlabel)
axes.set_ylim(ymin, ymax)
axes.set_ylabel(ylabel)
# Restore the unit data
axes.xaxis.converter = xconverter
axes.yaxis.converter = yconverter
axes.xaxis.set_units(xunits)
axes.yaxis.set_units(yunits)
axes.xaxis._update_axisinfo()
axes.yaxis._update_axisinfo()
# Set / Curves
for index, curve in enumerate(curves):
line = linedict[curvelabels[index]]
(label, linestyle, drawstyle, linewidth, color, marker, markersize,
markerfacecolor, markeredgecolor) = curve
line.set_label(label)
line.set_linestyle(linestyle)
line.set_drawstyle(drawstyle)
line.set_linewidth(linewidth)
rgba = mcolors.to_rgba(color)
line.set_alpha(None)
line.set_color(rgba)
if marker is not 'none':
line.set_marker(marker)
line.set_markersize(markersize)
line.set_markerfacecolor(markerfacecolor)
line.set_markeredgecolor(markeredgecolor)
# Set / Images
for index, image_settings in enumerate(images):
image = imagedict[imagelabels[index]]
label, cmap, low, high, interpolation = image_settings
image.set_label(label)
image.set_cmap(cm.get_cmap(cmap))
image.set_clim(*sorted([low, high]))
image.set_interpolation(interpolation)
# re-generate legend, if checkbox is checked
if generate_legend:
draggable = None
ncol = 1
if axes.legend_ is not None:
old_legend = axes.get_legend()
draggable = old_legend._draggable is not None
ncol = old_legend._ncol
new_legend = axes.legend(ncol=ncol)
if new_legend:
new_legend.set_draggable(draggable)
# Redraw
figure = axes.get_figure()
figure.canvas.draw()
if not (axes.get_xlim() == orig_xlim and axes.get_ylim() == orig_ylim):
figure.canvas.toolbar.push_current()
data = formlayout.fedit(datalist, title="Figure options", parent=parent,
icon=get_icon('qt4_editor_options.svg'),
apply=apply_callback)
if data is not None:
apply_callback(data)
@@ -1,542 +0,0 @@
"""
formlayout
==========
Module creating Qt form dialogs/layouts to edit various type of parameters
formlayout License Agreement (MIT License)
------------------------------------------
Copyright (c) 2009 Pierre Raybaut
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
# History:
# 1.0.10: added float validator (disable "Ok" and "Apply" button when not valid)
# 1.0.7: added support for "Apply" button
# 1.0.6: code cleaning
__version__ = '1.0.10'
__license__ = __doc__
import copy
import datetime
from numbers import Integral, Real
import warnings
from matplotlib import colors as mcolors
from matplotlib.backends.qt_compat import QtGui, QtWidgets, QtCore
BLACKLIST = {"title", "label"}
class ColorButton(QtWidgets.QPushButton):
"""
Color choosing push button
"""
colorChanged = QtCore.Signal(QtGui.QColor)
def __init__(self, parent=None):
QtWidgets.QPushButton.__init__(self, parent)
self.setFixedSize(20, 20)
self.setIconSize(QtCore.QSize(12, 12))
self.clicked.connect(self.choose_color)
self._color = QtGui.QColor()
def choose_color(self):
color = QtWidgets.QColorDialog.getColor(
self._color, self.parentWidget(), "",
QtWidgets.QColorDialog.ShowAlphaChannel)
if color.isValid():
self.set_color(color)
def get_color(self):
return self._color
@QtCore.Slot(QtGui.QColor)
def set_color(self, color):
if color != self._color:
self._color = color
self.colorChanged.emit(self._color)
pixmap = QtGui.QPixmap(self.iconSize())
pixmap.fill(color)
self.setIcon(QtGui.QIcon(pixmap))
color = QtCore.Property(QtGui.QColor, get_color, set_color)
def to_qcolor(color):
"""Create a QColor from a matplotlib color"""
qcolor = QtGui.QColor()
try:
rgba = mcolors.to_rgba(color)
except ValueError:
warnings.warn('Ignoring invalid color %r' % color, stacklevel=2)
return qcolor # return invalid QColor
qcolor.setRgbF(*rgba)
return qcolor
class ColorLayout(QtWidgets.QHBoxLayout):
"""Color-specialized QLineEdit layout"""
def __init__(self, color, parent=None):
QtWidgets.QHBoxLayout.__init__(self)
assert isinstance(color, QtGui.QColor)
self.lineedit = QtWidgets.QLineEdit(
mcolors.to_hex(color.getRgbF(), keep_alpha=True), parent)
self.lineedit.editingFinished.connect(self.update_color)
self.addWidget(self.lineedit)
self.colorbtn = ColorButton(parent)
self.colorbtn.color = color
self.colorbtn.colorChanged.connect(self.update_text)
self.addWidget(self.colorbtn)
def update_color(self):
color = self.text()
qcolor = to_qcolor(color)
self.colorbtn.color = qcolor # defaults to black if not qcolor.isValid()
def update_text(self, color):
self.lineedit.setText(mcolors.to_hex(color.getRgbF(), keep_alpha=True))
def text(self):
return self.lineedit.text()
def font_is_installed(font):
"""Check if font is installed"""
return [fam for fam in QtGui.QFontDatabase().families()
if str(fam) == font]
def tuple_to_qfont(tup):
"""
Create a QFont from tuple:
(family [string], size [int], italic [bool], bold [bool])
"""
if not (isinstance(tup, tuple) and len(tup) == 4
and font_is_installed(tup[0])
and isinstance(tup[1], Integral)
and isinstance(tup[2], bool)
and isinstance(tup[3], bool)):
return None
font = QtGui.QFont()
family, size, italic, bold = tup
font.setFamily(family)
font.setPointSize(size)
font.setItalic(italic)
font.setBold(bold)
return font
def qfont_to_tuple(font):
return (str(font.family()), int(font.pointSize()),
font.italic(), font.bold())
class FontLayout(QtWidgets.QGridLayout):
"""Font selection"""
def __init__(self, value, parent=None):
QtWidgets.QGridLayout.__init__(self)
font = tuple_to_qfont(value)
assert font is not None
# Font family
self.family = QtWidgets.QFontComboBox(parent)
self.family.setCurrentFont(font)
self.addWidget(self.family, 0, 0, 1, -1)
# Font size
self.size = QtWidgets.QComboBox(parent)
self.size.setEditable(True)
sizelist = [*range(6, 12), *range(12, 30, 2), 36, 48, 72]
size = font.pointSize()
if size not in sizelist:
sizelist.append(size)
sizelist.sort()
self.size.addItems([str(s) for s in sizelist])
self.size.setCurrentIndex(sizelist.index(size))
self.addWidget(self.size, 1, 0)
# Italic or not
self.italic = QtWidgets.QCheckBox(self.tr("Italic"), parent)
self.italic.setChecked(font.italic())
self.addWidget(self.italic, 1, 1)
# Bold or not
self.bold = QtWidgets.QCheckBox(self.tr("Bold"), parent)
self.bold.setChecked(font.bold())
self.addWidget(self.bold, 1, 2)
def get_font(self):
font = self.family.currentFont()
font.setItalic(self.italic.isChecked())
font.setBold(self.bold.isChecked())
font.setPointSize(int(self.size.currentText()))
return qfont_to_tuple(font)
def is_edit_valid(edit):
text = edit.text()
state = edit.validator().validate(text, 0)[0]
return state == QtGui.QDoubleValidator.Acceptable
class FormWidget(QtWidgets.QWidget):
update_buttons = QtCore.Signal()
def __init__(self, data, comment="", parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.data = copy.deepcopy(data)
self.widgets = []
self.formlayout = QtWidgets.QFormLayout(self)
if comment:
self.formlayout.addRow(QtWidgets.QLabel(comment))
self.formlayout.addRow(QtWidgets.QLabel(" "))
def get_dialog(self):
"""Return FormDialog instance"""
dialog = self.parent()
while not isinstance(dialog, QtWidgets.QDialog):
dialog = dialog.parent()
return dialog
def setup(self):
for label, value in self.data:
if label is None and value is None:
# Separator: (None, None)
self.formlayout.addRow(QtWidgets.QLabel(" "), QtWidgets.QLabel(" "))
self.widgets.append(None)
continue
elif label is None:
# Comment
self.formlayout.addRow(QtWidgets.QLabel(value))
self.widgets.append(None)
continue
elif tuple_to_qfont(value) is not None:
field = FontLayout(value, self)
elif (label.lower() not in BLACKLIST
and mcolors.is_color_like(value)):
field = ColorLayout(to_qcolor(value), self)
elif isinstance(value, str):
field = QtWidgets.QLineEdit(value, self)
elif isinstance(value, (list, tuple)):
if isinstance(value, tuple):
value = list(value)
# Note: get() below checks the type of value[0] in self.data so
# it is essential that value gets modified in-place.
# This means that the code is actually broken in the case where
# value is a tuple, but fortunately we always pass a list...
selindex = value.pop(0)
field = QtWidgets.QComboBox(self)
if isinstance(value[0], (list, tuple)):
keys = [key for key, _val in value]
value = [val for _key, val in value]
else:
keys = value
field.addItems(value)
if selindex in value:
selindex = value.index(selindex)
elif selindex in keys:
selindex = keys.index(selindex)
elif not isinstance(selindex, Integral):
warnings.warn(
"index '%s' is invalid (label: %s, value: %s)" %
(selindex, label, value), stacklevel=2)
selindex = 0
field.setCurrentIndex(selindex)
elif isinstance(value, bool):
field = QtWidgets.QCheckBox(self)
if value:
field.setCheckState(QtCore.Qt.Checked)
else:
field.setCheckState(QtCore.Qt.Unchecked)
elif isinstance(value, Integral):
field = QtWidgets.QSpinBox(self)
field.setRange(-1e9, 1e9)
field.setValue(value)
elif isinstance(value, Real):
field = QtWidgets.QLineEdit(repr(value), self)
field.setCursorPosition(0)
field.setValidator(QtGui.QDoubleValidator(field))
field.validator().setLocale(QtCore.QLocale("C"))
dialog = self.get_dialog()
dialog.register_float_field(field)
field.textChanged.connect(lambda text: dialog.update_buttons())
elif isinstance(value, datetime.datetime):
field = QtWidgets.QDateTimeEdit(self)
field.setDateTime(value)
elif isinstance(value, datetime.date):
field = QtWidgets.QDateEdit(self)
field.setDate(value)
else:
field = QtWidgets.QLineEdit(repr(value), self)
self.formlayout.addRow(label, field)
self.widgets.append(field)
def get(self):
valuelist = []
for index, (label, value) in enumerate(self.data):
field = self.widgets[index]
if label is None:
# Separator / Comment
continue
elif tuple_to_qfont(value) is not None:
value = field.get_font()
elif isinstance(value, str) or mcolors.is_color_like(value):
value = str(field.text())
elif isinstance(value, (list, tuple)):
index = int(field.currentIndex())
if isinstance(value[0], (list, tuple)):
value = value[index][0]
else:
value = value[index]
elif isinstance(value, bool):
value = field.checkState() == QtCore.Qt.Checked
elif isinstance(value, Integral):
value = int(field.value())
elif isinstance(value, Real):
value = float(str(field.text()))
elif isinstance(value, datetime.datetime):
value = field.dateTime().toPyDateTime()
elif isinstance(value, datetime.date):
value = field.date().toPyDate()
else:
value = eval(str(field.text()))
valuelist.append(value)
return valuelist
class FormComboWidget(QtWidgets.QWidget):
update_buttons = QtCore.Signal()
def __init__(self, datalist, comment="", parent=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.combobox = QtWidgets.QComboBox()
layout.addWidget(self.combobox)
self.stackwidget = QtWidgets.QStackedWidget(self)
layout.addWidget(self.stackwidget)
self.combobox.currentIndexChanged.connect(self.stackwidget.setCurrentIndex)
self.widgetlist = []
for data, title, comment in datalist:
self.combobox.addItem(title)
widget = FormWidget(data, comment=comment, parent=self)
self.stackwidget.addWidget(widget)
self.widgetlist.append(widget)
def setup(self):
for widget in self.widgetlist:
widget.setup()
def get(self):
return [widget.get() for widget in self.widgetlist]
class FormTabWidget(QtWidgets.QWidget):
update_buttons = QtCore.Signal()
def __init__(self, datalist, comment="", parent=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
self.tabwidget = QtWidgets.QTabWidget()
layout.addWidget(self.tabwidget)
self.setLayout(layout)
self.widgetlist = []
for data, title, comment in datalist:
if len(data[0]) == 3:
widget = FormComboWidget(data, comment=comment, parent=self)
else:
widget = FormWidget(data, comment=comment, parent=self)
index = self.tabwidget.addTab(widget, title)
self.tabwidget.setTabToolTip(index, comment)
self.widgetlist.append(widget)
def setup(self):
for widget in self.widgetlist:
widget.setup()
def get(self):
return [widget.get() for widget in self.widgetlist]
class FormDialog(QtWidgets.QDialog):
"""Form Dialog"""
def __init__(self, data, title="", comment="",
icon=None, parent=None, apply=None):
QtWidgets.QDialog.__init__(self, parent)
self.apply_callback = apply
# Form
if isinstance(data[0][0], (list, tuple)):
self.formwidget = FormTabWidget(data, comment=comment,
parent=self)
elif len(data[0]) == 3:
self.formwidget = FormComboWidget(data, comment=comment,
parent=self)
else:
self.formwidget = FormWidget(data, comment=comment,
parent=self)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.formwidget)
self.float_fields = []
self.formwidget.setup()
# Button box
self.bbox = bbox = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.formwidget.update_buttons.connect(self.update_buttons)
if self.apply_callback is not None:
apply_btn = bbox.addButton(QtWidgets.QDialogButtonBox.Apply)
apply_btn.clicked.connect(self.apply)
bbox.accepted.connect(self.accept)
bbox.rejected.connect(self.reject)
layout.addWidget(bbox)
self.setLayout(layout)
self.setWindowTitle(title)
if not isinstance(icon, QtGui.QIcon):
icon = QtWidgets.QWidget().style().standardIcon(QtWidgets.QStyle.SP_MessageBoxQuestion)
self.setWindowIcon(icon)
def register_float_field(self, field):
self.float_fields.append(field)
def update_buttons(self):
valid = True
for field in self.float_fields:
if not is_edit_valid(field):
valid = False
for btn_type in (QtWidgets.QDialogButtonBox.Ok,
QtWidgets.QDialogButtonBox.Apply):
btn = self.bbox.button(btn_type)
if btn is not None:
btn.setEnabled(valid)
def accept(self):
self.data = self.formwidget.get()
QtWidgets.QDialog.accept(self)
def reject(self):
self.data = None
QtWidgets.QDialog.reject(self)
def apply(self):
self.apply_callback(self.formwidget.get())
def get(self):
"""Return form result"""
return self.data
def fedit(data, title="", comment="", icon=None, parent=None, apply=None):
"""
Create form dialog and return result
(if Cancel button is pressed, return None)
data: datalist, datagroup
title: string
comment: string
icon: QIcon instance
parent: parent QWidget
apply: apply callback (function)
datalist: list/tuple of (field_name, field_value)
datagroup: list/tuple of (datalist *or* datagroup, title, comment)
-> one field for each member of a datalist
-> one tab for each member of a top-level datagroup
-> one page (of a multipage widget, each page can be selected with a combo
box) for each member of a datagroup inside a datagroup
Supported types for field_value:
- int, float, str, unicode, bool
- colors: in Qt-compatible text form, i.e. in hex format or name (red,...)
(automatically detected from a string)
- list/tuple:
* the first element will be the selected index (or value)
* the other elements can be couples (key, value) or only values
"""
# Create a QApplication instance if no instance currently exists
# (e.g., if the module is used directly from the interpreter)
if QtWidgets.QApplication.startingUp():
_app = QtWidgets.QApplication([])
dialog = FormDialog(data, title, comment, icon, parent, apply)
if dialog.exec_():
return dialog.get()
if __name__ == "__main__":
def create_datalist_example():
return [('str', 'this is a string'),
('list', [0, '1', '3', '4']),
('list2', ['--', ('none', 'None'), ('--', 'Dashed'),
('-.', 'DashDot'), ('-', 'Solid'),
('steps', 'Steps'), (':', 'Dotted')]),
('float', 1.2),
(None, 'Other:'),
('int', 12),
('font', ('Arial', 10, False, True)),
('color', '#123409'),
('bool', True),
('date', datetime.date(2010, 10, 10)),
('datetime', datetime.datetime(2010, 10, 10)),
]
def create_datagroup_example():
datalist = create_datalist_example()
return ((datalist, "Category 1", "Category 1 comment"),
(datalist, "Category 2", "Category 2 comment"),
(datalist, "Category 3", "Category 3 comment"))
#--------- datalist example
datalist = create_datalist_example()
def apply_test(data):
print("data:", data)
print("result:", fedit(datalist, title="Example",
comment="This is just an <b>example</b>.",
apply=apply_test))
#--------- datagroup example
datagroup = create_datagroup_example()
print("result:", fedit(datagroup, "Global title"))
#--------- datagroup inside a datagroup example
datalist = create_datalist_example()
datagroup = create_datagroup_example()
print("result:", fedit(((datagroup, "Title 1", "Tab 1 comment"),
(datalist, "Title 2", "Tab 2 comment"),
(datalist, "Title 3", "Tab 3 comment")),
"Global title"))
@@ -1,56 +0,0 @@
from matplotlib.backends.qt_compat import QtWidgets
class UiSubplotTool(QtWidgets.QDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setObjectName("SubplotTool")
self._widgets = {}
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
left = QtWidgets.QVBoxLayout()
layout.addLayout(left)
right = QtWidgets.QVBoxLayout()
layout.addLayout(right)
box = QtWidgets.QGroupBox("Borders")
left.addWidget(box)
inner = QtWidgets.QFormLayout(box)
for side in ["top", "bottom", "left", "right"]:
self._widgets[side] = widget = QtWidgets.QDoubleSpinBox()
widget.setMinimum(0)
widget.setMaximum(1)
widget.setDecimals(3)
widget.setSingleStep(.005)
widget.setKeyboardTracking(False)
inner.addRow(side, widget)
left.addStretch(1)
box = QtWidgets.QGroupBox("Spacings")
right.addWidget(box)
inner = QtWidgets.QFormLayout(box)
for side in ["hspace", "wspace"]:
self._widgets[side] = widget = QtWidgets.QDoubleSpinBox()
widget.setMinimum(0)
widget.setMaximum(1)
widget.setDecimals(3)
widget.setSingleStep(.005)
widget.setKeyboardTracking(False)
inner.addRow(side, widget)
right.addStretch(1)
widget = QtWidgets.QPushButton("Export values")
self._widgets["Export values"] = widget
# Don't trigger on <enter>, which is used to input values.
widget.setAutoDefault(False)
left.addWidget(widget)
for action in ["Tight layout", "Reset", "Close"]:
self._widgets[action] = widget = QtWidgets.QPushButton(action)
widget.setAutoDefault(False)
right.addWidget(widget)
self._widgets["Close"].setFocus()
@@ -1,46 +0,0 @@
import tkinter as Tk
import numpy as np
from matplotlib import cbook
from matplotlib.backends import _tkagg
cbook.warn_deprecated(
"3.0", "The matplotlib.backends.tkagg module is deprecated.")
def blit(photoimage, aggimage, bbox=None, colormode=1):
tk = photoimage.tk
if bbox is not None:
bbox_array = bbox.__array__()
# x1, x2, y1, y2
bboxptr = (bbox_array[0, 0], bbox_array[1, 0],
bbox_array[0, 1], bbox_array[1, 1])
else:
bboxptr = 0
data = np.asarray(aggimage)
dataptr = (data.shape[0], data.shape[1], data.ctypes.data)
try:
tk.call(
"PyAggImagePhoto", photoimage,
dataptr, colormode, bboxptr)
except Tk.TclError:
if hasattr(tk, 'interpaddr'):
_tkagg.tkinit(tk.interpaddr(), 1)
else:
# very old python?
_tkagg.tkinit(tk, 0)
tk.call("PyAggImagePhoto", photoimage,
dataptr, colormode, bboxptr)
def test(aggimage):
r = Tk.Tk()
c = Tk.Canvas(r, width=aggimage.width, height=aggimage.height)
c.pack()
p = Tk.PhotoImage(width=aggimage.width, height=aggimage.height)
blit(p, aggimage)
c.create_image(aggimage.width,aggimage.height,image=p)
blit(p, aggimage)
while True: r.update_idletasks()
@@ -1,43 +0,0 @@
<html>
<head>
<link rel="stylesheet" href="{{ prefix }}/_static/css/page.css" type="text/css">
<link rel="stylesheet" href="{{ prefix }}/_static/css/boilerplate.css" type="text/css" />
<link rel="stylesheet" href="{{ prefix }}/_static/css/fbm.css" type="text/css" />
<link rel="stylesheet" href="{{ prefix }}/_static/jquery/css/themes/base/jquery-ui.min.css" >
<script src="{{ prefix }}/_static/jquery/js/jquery-1.11.3.min.js"></script>
<script src="{{ prefix }}/_static/jquery/js/jquery-ui.min.js"></script>
<script src="{{ prefix }}/_static/js/mpl_tornado.js"></script>
<script src="{{ prefix }}/js/mpl.js"></script>
<script>
{% for (fig_id, fig_manager) in figures %}
$(document).ready(
function() {
var main_div = $('div#figures');
var figure_div = $('<div id="figure-div"/>')
main_div.append(figure_div);
var websocket_type = mpl.get_websocket_type();
var websocket = new websocket_type(
"{{ ws_uri }}" + "{{ fig_id }}" + "/ws");
var fig = new mpl.figure(
"{{ fig_id }}", websocket, mpl_ondownload, figure_div);
fig.focus_on_mouseover = true;
$(fig.canvas).attr('tabindex', {{ fig_id }});
}
);
{% end %}
</script>
<title>MPL | WebAgg current figures</title>
</head>
<body>
<div id="mpl-warnings" class="mpl-warnings"></div>
<div id="figures" style="margin: 10px 10px;"></div>
</body>
</html>
@@ -1,77 +0,0 @@
/**
* HTML5 Boilerplate
*
* style.css contains a reset, font normalization and some base styles.
*
* Credit is left where credit is due.
* Much inspiration was taken from these projects:
* - yui.yahooapis.com/2.8.1/build/base/base.css
* - camendesign.com/design/
* - praegnanz.de/weblog/htmlcssjs-kickstart
*/
/**
* html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
* v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
* html5doctor.com/html-5-reset-stylesheet/
*/
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
sup { vertical-align: super; }
sub { vertical-align: sub; }
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
blockquote, q { quotes: none; }
blockquote:before, blockquote:after,
q:before, q:after { content: ""; content: none; }
ins { background-color: #ff9; color: #000; text-decoration: none; }
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
del { text-decoration: line-through; }
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
table { border-collapse: collapse; border-spacing: 0; }
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
input, select { vertical-align: middle; }
/**
* Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
*/
body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
select, input, textarea, button { font:99% sans-serif; }
/* Normalize monospace sizing:
en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
pre, code, kbd, samp { font-family: monospace, sans-serif; }
em,i { font-style: italic; }
b,strong { font-weight: bold; }
@@ -1,97 +0,0 @@
/* Flexible box model classes */
/* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */
.hbox {
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-align: stretch;
display: -moz-box;
-moz-box-orient: horizontal;
-moz-box-align: stretch;
display: box;
box-orient: horizontal;
box-align: stretch;
}
.hbox > * {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
}
.vbox {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-box-align: stretch;
display: -moz-box;
-moz-box-orient: vertical;
-moz-box-align: stretch;
display: box;
box-orient: vertical;
box-align: stretch;
}
.vbox > * {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
}
.reverse {
-webkit-box-direction: reverse;
-moz-box-direction: reverse;
box-direction: reverse;
}
.box-flex0 {
-webkit-box-flex: 0;
-moz-box-flex: 0;
box-flex: 0;
}
.box-flex1, .box-flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
box-flex: 1;
}
.box-flex2 {
-webkit-box-flex: 2;
-moz-box-flex: 2;
box-flex: 2;
}
.box-group1 {
-webkit-box-flex-group: 1;
-moz-box-flex-group: 1;
box-flex-group: 1;
}
.box-group2 {
-webkit-box-flex-group: 2;
-moz-box-flex-group: 2;
box-flex-group: 2;
}
.start {
-webkit-box-pack: start;
-moz-box-pack: start;
box-pack: start;
}
.end {
-webkit-box-pack: end;
-moz-box-pack: end;
box-pack: end;
}
.center {
-webkit-box-pack: center;
-moz-box-pack: center;
box-pack: center;
}
@@ -1,82 +0,0 @@
/**
* Primary styles
*
* Author: IPython Development Team
*/
body {
background-color: white;
/* This makes sure that the body covers the entire window and needs to
be in a different element than the display: box in wrapper below */
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
overflow: visible;
}
div#header {
/* Initially hidden to prevent FLOUC */
display: none;
position: relative;
height: 40px;
padding: 5px;
margin: 0px;
width: 100%;
}
span#ipython_notebook {
position: absolute;
padding: 2px 2px 2px 5px;
}
span#ipython_notebook img {
font-family: Verdana, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
height: 24px;
text-decoration:none;
display: inline;
color: black;
}
#site {
width: 100%;
display: none;
}
/* We set the fonts by hand here to override the values in the theme */
.ui-widget {
font-family: "Lucinda Grande", "Lucinda Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
}
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {
font-family: "Lucinda Grande", "Lucinda Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
}
/* Smaller buttons */
.ui-button .ui-button-text {
padding: 0.2em 0.8em;
font-size: 77%;
}
input.ui-button {
padding: 0.3em 0.9em;
}
span#login_widget {
float: right;
}
.border-box-sizing {
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
#figure-div {
display: inline-block;
margin: 10px;
}
@@ -1,34 +0,0 @@
<!-- Within the kernel, we don't know the address of the matplotlib
websocket server, so we have to get in client-side and fetch our
resources that way. -->
<script>
// We can't proceed until these Javascript files are fetched, so
// we fetch them synchronously
$.ajaxSetup({async: false});
$.getScript("http://" + window.location.hostname + ":{{ port }}{{prefix}}/_static/js/mpl_tornado.js");
$.getScript("http://" + window.location.hostname + ":{{ port }}{{prefix}}/js/mpl.js");
$.ajaxSetup({async: true});
function init_figure{{ fig_id }}(e) {
$('div.output').off('resize');
var output_div = $(e.target).find('div.output_subarea');
var websocket_type = mpl.get_websocket_type();
var websocket = new websocket_type(
"ws://" + window.location.hostname + ":{{ port }}{{ prefix}}/" +
{{ repr(str(fig_id)) }} + "/ws");
var fig = new mpl.figure(
{{repr(str(fig_id))}}, websocket, mpl_ondownload, output_div);
// Fetch the first image
fig.context.drawImage(fig.imageObj, 0, 0);
fig.focus_on_mouseover = true;
}
// We can't initialize the figure contents until our content
// has been added to the DOM. This is a bit of hack to get an
// event for that.
$('div.output').resize(init_figure{{ fig_id }});
</script>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,554 +0,0 @@
/* Put everything inside the global mpl namespace */
window.mpl = {};
mpl.get_websocket_type = function() {
if (typeof(WebSocket) !== 'undefined') {
return WebSocket;
} else if (typeof(MozWebSocket) !== 'undefined') {
return MozWebSocket;
} else {
alert('Your browser does not have WebSocket support.' +
'Please try Chrome, Safari or Firefox ≥ 6. ' +
'Firefox 4 and 5 are also supported but you ' +
'have to enable WebSockets in about:config.');
};
}
mpl.figure = function(figure_id, websocket, ondownload, parent_element) {
this.id = figure_id;
this.ws = websocket;
this.supports_binary = (this.ws.binaryType != undefined);
if (!this.supports_binary) {
var warnings = document.getElementById("mpl-warnings");
if (warnings) {
warnings.style.display = 'block';
warnings.textContent = (
"This browser does not support binary websocket messages. " +
"Performance may be slow.");
}
}
this.imageObj = new Image();
this.context = undefined;
this.message = undefined;
this.canvas = undefined;
this.rubberband_canvas = undefined;
this.rubberband_context = undefined;
this.format_dropdown = undefined;
this.image_mode = 'full';
this.root = $('<div/>');
this._root_extra_style(this.root)
this.root.attr('style', 'display: inline-block');
$(parent_element).append(this.root);
this._init_header(this);
this._init_canvas(this);
this._init_toolbar(this);
var fig = this;
this.waiting = false;
this.ws.onopen = function () {
fig.send_message("supports_binary", {value: fig.supports_binary});
fig.send_message("send_image_mode", {});
if (mpl.ratio != 1) {
fig.send_message("set_dpi_ratio", {'dpi_ratio': mpl.ratio});
}
fig.send_message("refresh", {});
}
this.imageObj.onload = function() {
if (fig.image_mode == 'full') {
// Full images could contain transparency (where diff images
// almost always do), so we need to clear the canvas so that
// there is no ghosting.
fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);
}
fig.context.drawImage(fig.imageObj, 0, 0);
};
this.imageObj.onunload = function() {
fig.ws.close();
}
this.ws.onmessage = this._make_on_message_function(this);
this.ondownload = ondownload;
}
mpl.figure.prototype._init_header = function() {
var titlebar = $(
'<div class="ui-dialog-titlebar ui-widget-header ui-corner-all ' +
'ui-helper-clearfix"/>');
var titletext = $(
'<div class="ui-dialog-title" style="width: 100%; ' +
'text-align: center; padding: 3px;"/>');
titlebar.append(titletext)
this.root.append(titlebar);
this.header = titletext[0];
}
mpl.figure.prototype._canvas_extra_style = function(canvas_div) {
}
mpl.figure.prototype._root_extra_style = function(canvas_div) {
}
mpl.figure.prototype._init_canvas = function() {
var fig = this;
var canvas_div = $('<div/>');
canvas_div.attr('style', 'position: relative; clear: both; outline: 0');
function canvas_keyboard_event(event) {
return fig.key_event(event, event['data']);
}
canvas_div.keydown('key_press', canvas_keyboard_event);
canvas_div.keyup('key_release', canvas_keyboard_event);
this.canvas_div = canvas_div
this._canvas_extra_style(canvas_div)
this.root.append(canvas_div);
var canvas = $('<canvas/>');
canvas.addClass('mpl-canvas');
canvas.attr('style', "left: 0; top: 0; z-index: 0; outline: 0")
this.canvas = canvas[0];
this.context = canvas[0].getContext("2d");
var backingStore = this.context.backingStorePixelRatio ||
this.context.webkitBackingStorePixelRatio ||
this.context.mozBackingStorePixelRatio ||
this.context.msBackingStorePixelRatio ||
this.context.oBackingStorePixelRatio ||
this.context.backingStorePixelRatio || 1;
mpl.ratio = (window.devicePixelRatio || 1) / backingStore;
var rubberband = $('<canvas/>');
rubberband.attr('style', "position: absolute; left: 0; top: 0; z-index: 1;")
var pass_mouse_events = true;
canvas_div.resizable({
start: function(event, ui) {
pass_mouse_events = false;
},
resize: function(event, ui) {
fig.request_resize(ui.size.width, ui.size.height);
},
stop: function(event, ui) {
pass_mouse_events = true;
fig.request_resize(ui.size.width, ui.size.height);
},
});
function mouse_event_fn(event) {
if (pass_mouse_events)
return fig.mouse_event(event, event['data']);
}
rubberband.mousedown('button_press', mouse_event_fn);
rubberband.mouseup('button_release', mouse_event_fn);
// Throttle sequential mouse events to 1 every 20ms.
rubberband.mousemove('motion_notify', mouse_event_fn);
rubberband.mouseenter('figure_enter', mouse_event_fn);
rubberband.mouseleave('figure_leave', mouse_event_fn);
canvas_div.on("wheel", function (event) {
event = event.originalEvent;
event['data'] = 'scroll'
if (event.deltaY < 0) {
event.step = 1;
} else {
event.step = -1;
}
mouse_event_fn(event);
});
canvas_div.append(canvas);
canvas_div.append(rubberband);
this.rubberband = rubberband;
this.rubberband_canvas = rubberband[0];
this.rubberband_context = rubberband[0].getContext("2d");
this.rubberband_context.strokeStyle = "#000000";
this._resize_canvas = function(width, height) {
// Keep the size of the canvas, canvas container, and rubber band
// canvas in synch.
canvas_div.css('width', width)
canvas_div.css('height', height)
canvas.attr('width', width * mpl.ratio);
canvas.attr('height', height * mpl.ratio);
canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');
rubberband.attr('width', width);
rubberband.attr('height', height);
}
// Set the figure to an initial 600x600px, this will subsequently be updated
// upon first draw.
this._resize_canvas(600, 600);
// Disable right mouse context menu.
$(this.rubberband_canvas).bind("contextmenu",function(e){
return false;
});
function set_focus () {
canvas.focus();
canvas_div.focus();
}
window.setTimeout(set_focus, 100);
}
mpl.figure.prototype._init_toolbar = function() {
var fig = this;
var nav_element = $('<div/>')
nav_element.attr('style', 'width: 100%');
this.root.append(nav_element);
// Define a callback function for later on.
function toolbar_event(event) {
return fig.toolbar_button_onclick(event['data']);
}
function toolbar_mouse_event(event) {
return fig.toolbar_button_onmouseover(event['data']);
}
for(var toolbar_ind in mpl.toolbar_items) {
var name = mpl.toolbar_items[toolbar_ind][0];
var tooltip = mpl.toolbar_items[toolbar_ind][1];
var image = mpl.toolbar_items[toolbar_ind][2];
var method_name = mpl.toolbar_items[toolbar_ind][3];
if (!name) {
// put a spacer in here.
continue;
}
var button = $('<button/>');
button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +
'ui-button-icon-only');
button.attr('role', 'button');
button.attr('aria-disabled', 'false');
button.click(method_name, toolbar_event);
button.mouseover(tooltip, toolbar_mouse_event);
var icon_img = $('<span/>');
icon_img.addClass('ui-button-icon-primary ui-icon');
icon_img.addClass(image);
icon_img.addClass('ui-corner-all');
var tooltip_span = $('<span/>');
tooltip_span.addClass('ui-button-text');
tooltip_span.html(tooltip);
button.append(icon_img);
button.append(tooltip_span);
nav_element.append(button);
}
var fmt_picker_span = $('<span/>');
var fmt_picker = $('<select/>');
fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');
fmt_picker_span.append(fmt_picker);
nav_element.append(fmt_picker_span);
this.format_dropdown = fmt_picker[0];
for (var ind in mpl.extensions) {
var fmt = mpl.extensions[ind];
var option = $(
'<option/>', {selected: fmt === mpl.default_extension}).html(fmt);
fmt_picker.append(option)
}
// Add hover states to the ui-buttons
$( ".ui-button" ).hover(
function() { $(this).addClass("ui-state-hover");},
function() { $(this).removeClass("ui-state-hover");}
);
var status_bar = $('<span class="mpl-message"/>');
nav_element.append(status_bar);
this.message = status_bar[0];
}
mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {
// Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,
// which will in turn request a refresh of the image.
this.send_message('resize', {'width': x_pixels, 'height': y_pixels});
}
mpl.figure.prototype.send_message = function(type, properties) {
properties['type'] = type;
properties['figure_id'] = this.id;
this.ws.send(JSON.stringify(properties));
}
mpl.figure.prototype.send_draw_message = function() {
if (!this.waiting) {
this.waiting = true;
this.ws.send(JSON.stringify({type: "draw", figure_id: this.id}));
}
}
mpl.figure.prototype.handle_save = function(fig, msg) {
var format_dropdown = fig.format_dropdown;
var format = format_dropdown.options[format_dropdown.selectedIndex].value;
fig.ondownload(fig, format);
}
mpl.figure.prototype.handle_resize = function(fig, msg) {
var size = msg['size'];
if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {
fig._resize_canvas(size[0], size[1]);
fig.send_message("refresh", {});
};
}
mpl.figure.prototype.handle_rubberband = function(fig, msg) {
var x0 = msg['x0'] / mpl.ratio;
var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;
var x1 = msg['x1'] / mpl.ratio;
var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;
x0 = Math.floor(x0) + 0.5;
y0 = Math.floor(y0) + 0.5;
x1 = Math.floor(x1) + 0.5;
y1 = Math.floor(y1) + 0.5;
var min_x = Math.min(x0, x1);
var min_y = Math.min(y0, y1);
var width = Math.abs(x1 - x0);
var height = Math.abs(y1 - y0);
fig.rubberband_context.clearRect(
0, 0, fig.canvas.width, fig.canvas.height);
fig.rubberband_context.strokeRect(min_x, min_y, width, height);
}
mpl.figure.prototype.handle_figure_label = function(fig, msg) {
// Updates the figure title.
fig.header.textContent = msg['label'];
}
mpl.figure.prototype.handle_cursor = function(fig, msg) {
var cursor = msg['cursor'];
switch(cursor)
{
case 0:
cursor = 'pointer';
break;
case 1:
cursor = 'default';
break;
case 2:
cursor = 'crosshair';
break;
case 3:
cursor = 'move';
break;
}
fig.rubberband_canvas.style.cursor = cursor;
}
mpl.figure.prototype.handle_message = function(fig, msg) {
fig.message.textContent = msg['message'];
}
mpl.figure.prototype.handle_draw = function(fig, msg) {
// Request the server to send over a new figure.
fig.send_draw_message();
}
mpl.figure.prototype.handle_image_mode = function(fig, msg) {
fig.image_mode = msg['mode'];
}
mpl.figure.prototype.updated_canvas_event = function() {
// Called whenever the canvas gets updated.
this.send_message("ack", {});
}
// A function to construct a web socket function for onmessage handling.
// Called in the figure constructor.
mpl.figure.prototype._make_on_message_function = function(fig) {
return function socket_on_message(evt) {
if (evt.data instanceof Blob) {
/* FIXME: We get "Resource interpreted as Image but
* transferred with MIME type text/plain:" errors on
* Chrome. But how to set the MIME type? It doesn't seem
* to be part of the websocket stream */
evt.data.type = "image/png";
/* Free the memory for the previous frames */
if (fig.imageObj.src) {
(window.URL || window.webkitURL).revokeObjectURL(
fig.imageObj.src);
}
fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(
evt.data);
fig.updated_canvas_event();
fig.waiting = false;
return;
}
else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == "data:image/png;base64") {
fig.imageObj.src = evt.data;
fig.updated_canvas_event();
fig.waiting = false;
return;
}
var msg = JSON.parse(evt.data);
var msg_type = msg['type'];
// Call the "handle_{type}" callback, which takes
// the figure and JSON message as its only arguments.
try {
var callback = fig["handle_" + msg_type];
} catch (e) {
console.log("No handler for the '" + msg_type + "' message type: ", msg);
return;
}
if (callback) {
try {
// console.log("Handling '" + msg_type + "' message: ", msg);
callback(fig, msg);
} catch (e) {
console.log("Exception inside the 'handler_" + msg_type + "' callback:", e, e.stack, msg);
}
}
};
}
// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas
mpl.findpos = function(e) {
//this section is from http://www.quirksmode.org/js/events_properties.html
var targ;
if (!e)
e = window.event;
if (e.target)
targ = e.target;
else if (e.srcElement)
targ = e.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
// jQuery normalizes the pageX and pageY
// pageX,Y are the mouse positions relative to the document
// offset() returns the position of the element relative to the document
var x = e.pageX - $(targ).offset().left;
var y = e.pageY - $(targ).offset().top;
return {"x": x, "y": y};
};
/*
* return a copy of an object with only non-object keys
* we need this to avoid circular references
* http://stackoverflow.com/a/24161582/3208463
*/
function simpleKeys (original) {
return Object.keys(original).reduce(function (obj, key) {
if (typeof original[key] !== 'object')
obj[key] = original[key]
return obj;
}, {});
}
mpl.figure.prototype.mouse_event = function(event, name) {
var canvas_pos = mpl.findpos(event)
if (name === 'button_press')
{
this.canvas.focus();
this.canvas_div.focus();
}
var x = canvas_pos.x * mpl.ratio;
var y = canvas_pos.y * mpl.ratio;
this.send_message(name, {x: x, y: y, button: event.button,
step: event.step,
guiEvent: simpleKeys(event)});
/* This prevents the web browser from automatically changing to
* the text insertion cursor when the button is pressed. We want
* to control all of the cursor setting manually through the
* 'cursor' event from matplotlib */
event.preventDefault();
return false;
}
mpl.figure.prototype._key_event_extra = function(event, name) {
// Handle any extra behaviour associated with a key event
}
mpl.figure.prototype.key_event = function(event, name) {
// Prevent repeat events
if (name == 'key_press')
{
if (event.which === this._key)
return;
else
this._key = event.which;
}
if (name == 'key_release')
this._key = null;
var value = '';
if (event.ctrlKey && event.which != 17)
value += "ctrl+";
if (event.altKey && event.which != 18)
value += "alt+";
if (event.shiftKey && event.which != 16)
value += "shift+";
value += 'k';
value += event.which.toString();
this._key_event_extra(event, name);
this.send_message(name, {key: value,
guiEvent: simpleKeys(event)});
return false;
}
mpl.figure.prototype.toolbar_button_onclick = function(name) {
if (name == 'download') {
this.handle_save(this, null);
} else {
this.send_message("toolbar_button", {name: name});
}
};
mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {
this.message.textContent = tooltip;
};
@@ -1,7 +0,0 @@
/* This .js file contains functions for matplotlib's built-in
tornado-based server, that are not relevant when embedding WebAgg
in another web application. */
function mpl_ondownload(figure, format) {
window.open(figure.id + '/download.' + format, '_blank');
}
@@ -1,211 +0,0 @@
var comm_websocket_adapter = function(comm) {
// Create a "websocket"-like object which calls the given IPython comm
// object with the appropriate methods. Currently this is a non binary
// socket, so there is still some room for performance tuning.
var ws = {};
ws.close = function() {
comm.close()
};
ws.send = function(m) {
//console.log('sending', m);
comm.send(m);
};
// Register the callback with on_msg.
comm.on_msg(function(msg) {
//console.log('receiving', msg['content']['data'], msg);
// Pass the mpl event to the overridden (by mpl) onmessage function.
ws.onmessage(msg['content']['data'])
});
return ws;
}
mpl.mpl_figure_comm = function(comm, msg) {
// This is the function which gets called when the mpl process
// starts-up an IPython Comm through the "matplotlib" channel.
var id = msg.content.data.id;
// Get hold of the div created by the display call when the Comm
// socket was opened in Python.
var element = $("#" + id);
var ws_proxy = comm_websocket_adapter(comm)
function ondownload(figure, format) {
window.open(figure.imageObj.src);
}
var fig = new mpl.figure(id, ws_proxy,
ondownload,
element.get(0));
// Call onopen now - mpl needs it, as it is assuming we've passed it a real
// web socket which is closed, not our websocket->open comm proxy.
ws_proxy.onopen();
fig.parent_element = element.get(0);
fig.cell_info = mpl.find_output_cell("<div id='" + id + "'></div>");
if (!fig.cell_info) {
console.error("Failed to find cell for figure", id, fig);
return;
}
var output_index = fig.cell_info[2]
var cell = fig.cell_info[0];
};
mpl.figure.prototype.handle_close = function(fig, msg) {
var width = fig.canvas.width/mpl.ratio
fig.root.unbind('remove')
// Update the output cell to use the data from the current canvas.
fig.push_to_output();
var dataURL = fig.canvas.toDataURL();
// Re-enable the keyboard manager in IPython - without this line, in FF,
// the notebook keyboard shortcuts fail.
IPython.keyboard_manager.enable()
$(fig.parent_element).html('<img src="' + dataURL + '" width="' + width + '">');
fig.close_ws(fig, msg);
}
mpl.figure.prototype.close_ws = function(fig, msg){
fig.send_message('closing', msg);
// fig.ws.close()
}
mpl.figure.prototype.push_to_output = function(remove_interactive) {
// Turn the data on the canvas into data in the output cell.
var width = this.canvas.width/mpl.ratio
var dataURL = this.canvas.toDataURL();
this.cell_info[1]['text/html'] = '<img src="' + dataURL + '" width="' + width + '">';
}
mpl.figure.prototype.updated_canvas_event = function() {
// Tell IPython that the notebook contents must change.
IPython.notebook.set_dirty(true);
this.send_message("ack", {});
var fig = this;
// Wait a second, then push the new image to the DOM so
// that it is saved nicely (might be nice to debounce this).
setTimeout(function () { fig.push_to_output() }, 1000);
}
mpl.figure.prototype._init_toolbar = function() {
var fig = this;
var nav_element = $('<div/>')
nav_element.attr('style', 'width: 100%');
this.root.append(nav_element);
// Define a callback function for later on.
function toolbar_event(event) {
return fig.toolbar_button_onclick(event['data']);
}
function toolbar_mouse_event(event) {
return fig.toolbar_button_onmouseover(event['data']);
}
for(var toolbar_ind in mpl.toolbar_items){
var name = mpl.toolbar_items[toolbar_ind][0];
var tooltip = mpl.toolbar_items[toolbar_ind][1];
var image = mpl.toolbar_items[toolbar_ind][2];
var method_name = mpl.toolbar_items[toolbar_ind][3];
if (!name) { continue; };
var button = $('<button class="btn btn-default" href="#" title="' + name + '"><i class="fa ' + image + ' fa-lg"></i></button>');
button.click(method_name, toolbar_event);
button.mouseover(tooltip, toolbar_mouse_event);
nav_element.append(button);
}
// Add the status bar.
var status_bar = $('<span class="mpl-message" style="text-align:right; float: right;"/>');
nav_element.append(status_bar);
this.message = status_bar[0];
// Add the close button to the window.
var buttongrp = $('<div class="btn-group inline pull-right"></div>');
var button = $('<button class="btn btn-mini btn-primary" href="#" title="Stop Interaction"><i class="fa fa-power-off icon-remove icon-large"></i></button>');
button.click(function (evt) { fig.handle_close(fig, {}); } );
button.mouseover('Stop Interaction', toolbar_mouse_event);
buttongrp.append(button);
var titlebar = this.root.find($('.ui-dialog-titlebar'));
titlebar.prepend(buttongrp);
}
mpl.figure.prototype._root_extra_style = function(el){
var fig = this
el.on("remove", function(){
fig.close_ws(fig, {});
});
}
mpl.figure.prototype._canvas_extra_style = function(el){
// this is important to make the div 'focusable
el.attr('tabindex', 0)
// reach out to IPython and tell the keyboard manager to turn it's self
// off when our div gets focus
// location in version 3
if (IPython.notebook.keyboard_manager) {
IPython.notebook.keyboard_manager.register_events(el);
}
else {
// location in version 2
IPython.keyboard_manager.register_events(el);
}
}
mpl.figure.prototype._key_event_extra = function(event, name) {
var manager = IPython.notebook.keyboard_manager;
if (!manager)
manager = IPython.keyboard_manager;
// Check for shift+enter
if (event.shiftKey && event.which == 13) {
this.canvas_div.blur();
event.shiftKey = false;
// Send a "J" for go to next cell
event.which = 74;
event.keyCode = 74;
manager.command_mode();
manager.handle_keydown(event);
}
}
mpl.figure.prototype.handle_save = function(fig, msg) {
fig.ondownload(fig, null);
}
mpl.find_output_cell = function(html_output) {
// Return the cell and output element which can be found *uniquely* in the notebook.
// Note - this is a bit hacky, but it is done because the "notebook_saving.Notebook"
// IPython event is triggered only after the cells have been serialised, which for
// our purposes (turning an active figure into a static one), is too late.
var cells = IPython.notebook.get_cells();
var ncells = cells.length;
for (var i=0; i<ncells; i++) {
var cell = cells[i];
if (cell.cell_type === 'code'){
for (var j=0; j<cell.output_area.outputs.length; j++) {
var data = cell.output_area.outputs[j];
if (data.data) {
// IPython >= 3 moved mimebundle to data attribute of output
data = data.data;
}
if (data['text/html'] == html_output) {
return [cell, data, j];
}
}
}
}
}
// Register the function which deals with the matplotlib target/channel.
// The kernel may be null if the page has been refreshed.
if (IPython.notebook.kernel != null) {
IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);
}
@@ -1,639 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from imp import reload"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## UAT for NbAgg backend.\n",
"\n",
"The first line simply reloads matplotlib, uses the nbagg backend and then reloads the backend, just to ensure we have the latest modification to the backend code. Note: The underlying JavaScript will not be updated by this process, so a refresh of the browser after clearing the output and saving is necessary to clear everything fully."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib\n",
"reload(matplotlib)\n",
"\n",
"matplotlib.use('nbagg')\n",
"\n",
"import matplotlib.backends.backend_nbagg\n",
"reload(matplotlib.backends.backend_nbagg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 1 - Simple figure creation using pyplot\n",
"\n",
"Should produce a figure window which is interactive with the pan and zoom buttons. (Do not press the close button, but any others may be used)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.backends.backend_webagg_core\n",
"reload(matplotlib.backends.backend_webagg_core)\n",
"\n",
"import matplotlib.pyplot as plt\n",
"plt.interactive(False)\n",
"\n",
"fig1 = plt.figure()\n",
"plt.plot(range(10))\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 2 - Creation of another figure, without the need to do plt.figure.\n",
"\n",
"As above, a new figure should be created."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.plot([3, 2, 1])\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 3 - Connection info\n",
"\n",
"The printout should show that there are two figures which have active CommSockets, and no figures pending show."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(matplotlib.backends.backend_nbagg.connection_info())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 4 - Closing figures\n",
"\n",
"Closing a specific figure instance should turn the figure into a plain image - the UI should have been removed. In this case, scroll back to the first figure and assert this is the case."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.close(fig1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 5 - No show without plt.show in non-interactive mode\n",
"\n",
"Simply doing a plt.plot should not show a new figure, nor indeed update an existing one (easily verified in UAT 6).\n",
"The output should simply be a list of Line2D instances."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.plot(range(10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 6 - Connection information\n",
"\n",
"We just created a new figure, but didn't show it. Connection info should no longer have \"Figure 1\" (as we closed it in UAT 4) and should have figure 2 and 3, with Figure 3 without any connections. There should be 1 figure pending."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(matplotlib.backends.backend_nbagg.connection_info())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 7 - Show of previously created figure\n",
"\n",
"We should be able to show a figure we've previously created. The following should produce two figure windows."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.show()\n",
"plt.figure()\n",
"plt.plot(range(5))\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 8 - Interactive mode\n",
"\n",
"In interactive mode, creating a line should result in a figure being shown."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.interactive(True)\n",
"plt.figure()\n",
"plt.plot([3, 2, 1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Subsequent lines should be added to the existing figure, rather than creating a new one."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.plot(range(3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Calling connection_info in interactive mode should not show any pending figures."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(matplotlib.backends.backend_nbagg.connection_info())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Disable interactive mode again."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.interactive(False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 9 - Multiple shows\n",
"\n",
"Unlike most of the other matplotlib backends, we may want to see a figure multiple times (with or without synchronisation between the views, though the former is not yet implemented). Assert that plt.gcf().canvas.manager.reshow() results in another figure window which is synchronised upon pan & zoom."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"plt.gcf().canvas.manager.reshow()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 10 - Saving notebook\n",
"\n",
"Saving the notebook (with CTRL+S or File->Save) should result in the saved notebook having static versions of the figues embedded within. The image should be the last update from user interaction and interactive plotting. (check by converting with ``ipython nbconvert <notebook>``)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 11 - Creation of a new figure on second show\n",
"\n",
"Create a figure, show it, then create a new axes and show it. The result should be a new figure.\n",
"\n",
"**BUG: Sometimes this doesn't work - not sure why (@pelson).**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig = plt.figure()\n",
"plt.axes()\n",
"plt.show()\n",
"\n",
"plt.plot([1, 2, 3])\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 12 - OO interface\n",
"\n",
"Should produce a new figure and plot it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from matplotlib.backends.backend_nbagg import new_figure_manager,show\n",
"\n",
"manager = new_figure_manager(1000)\n",
"fig = manager.canvas.figure\n",
"ax = fig.add_subplot(1,1,1)\n",
"ax.plot([1,2,3])\n",
"fig.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## UAT 13 - Animation\n",
"\n",
"The following should generate an animated line:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.animation as animation\n",
"import numpy as np\n",
"\n",
"fig, ax = plt.subplots()\n",
"\n",
"x = np.arange(0, 2*np.pi, 0.01) # x-array\n",
"line, = ax.plot(x, np.sin(x))\n",
"\n",
"def animate(i):\n",
" line.set_ydata(np.sin(x+i/10.0)) # update the data\n",
" return line,\n",
"\n",
"#Init only required for blitting to give a clean slate.\n",
"def init():\n",
" line.set_ydata(np.ma.array(x, mask=True))\n",
" return line,\n",
"\n",
"ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,\n",
" interval=32., blit=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 14 - Keyboard shortcuts in IPython after close of figure\n",
"\n",
"After closing the previous figure (with the close button above the figure) the IPython keyboard shortcuts should still function.\n",
"\n",
"### UAT 15 - Figure face colours\n",
"\n",
"The nbagg honours all colours apart from that of the figure.patch. The two plots below should produce a figure with a red background. There should be no yellow figure."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib\n",
"matplotlib.rcParams.update({'figure.facecolor': 'red',\n",
" 'savefig.facecolor': 'yellow'})\n",
"plt.figure()\n",
"plt.plot([3, 2, 1])\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 16 - Events\n",
"\n",
"Pressing any keyboard key or mouse button (or scrolling) should cycle the line line while the figure has focus. The figure should have focus by default when it is created and re-gain it by clicking on the canvas. Clicking anywhere outside of the figure should release focus, but moving the mouse out of the figure should not release focus."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import itertools\n",
"fig, ax = plt.subplots()\n",
"x = np.linspace(0,10,10000)\n",
"y = np.sin(x)\n",
"ln, = ax.plot(x,y)\n",
"evt = []\n",
"colors = iter(itertools.cycle(['r', 'g', 'b', 'k', 'c']))\n",
"def on_event(event):\n",
" if event.name.startswith('key'):\n",
" fig.suptitle('%s: %s' % (event.name, event.key))\n",
" elif event.name == 'scroll_event':\n",
" fig.suptitle('%s: %s' % (event.name, event.step))\n",
" else:\n",
" fig.suptitle('%s: %s' % (event.name, event.button))\n",
" evt.append(event)\n",
" ln.set_color(next(colors))\n",
" fig.canvas.draw()\n",
" fig.canvas.draw_idle()\n",
"\n",
"fig.canvas.mpl_connect('button_press_event', on_event)\n",
"fig.canvas.mpl_connect('button_release_event', on_event)\n",
"fig.canvas.mpl_connect('scroll_event', on_event)\n",
"fig.canvas.mpl_connect('key_press_event', on_event)\n",
"fig.canvas.mpl_connect('key_release_event', on_event)\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT 17 - Timers\n",
"\n",
"Single-shot timers follow a completely different code path in the nbagg backend than regular timers (such as those used in the animation example above.) The next set of tests ensures that both \"regular\" and \"single-shot\" timers work properly.\n",
"\n",
"The following should show a simple clock that updates twice a second:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import time\n",
"\n",
"fig, ax = plt.subplots()\n",
"text = ax.text(0.5, 0.5, '', ha='center')\n",
"\n",
"def update(text):\n",
" text.set(text=time.ctime())\n",
" text.axes.figure.canvas.draw()\n",
" \n",
"timer = fig.canvas.new_timer(500, [(update, [text], {})])\n",
"timer.start()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, the following should only update once and then stop:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig, ax = plt.subplots()\n",
"text = ax.text(0.5, 0.5, '', ha='center') \n",
"timer = fig.canvas.new_timer(500, [(update, [text], {})])\n",
"\n",
"timer.single_shot = True\n",
"timer.start()\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And the next two examples should never show any visible text at all:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig, ax = plt.subplots()\n",
"text = ax.text(0.5, 0.5, '', ha='center')\n",
"timer = fig.canvas.new_timer(500, [(update, [text], {})])\n",
"\n",
"timer.start()\n",
"timer.stop()\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig, ax = plt.subplots()\n",
"text = ax.text(0.5, 0.5, '', ha='center')\n",
"timer = fig.canvas.new_timer(500, [(update, [text], {})])\n",
"\n",
"timer.single_shot = True\n",
"timer.start()\n",
"timer.stop()\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### UAT17 - stopping figure when removed from DOM\n",
"\n",
"When the div that contains from the figure is removed from the DOM the figure should shut down it's comm, and if the python-side figure has no more active comms, it should destroy the figure. Repeatedly running the cell below should always have the same figure number"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig, ax = plt.subplots()\n",
"ax.plot(range(5))\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Running the cell below will re-show the figure. After this, re-running the cell above should result in a new figure number."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig.canvas.manager.reshow()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.4.3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
@@ -1,30 +0,0 @@
<html>
<head>
<link rel="stylesheet" href="{{ prefix }}/_static/css/page.css" type="text/css">
<link rel="stylesheet" href="{{ prefix }}/_static/css/boilerplate.css" type="text/css" />
<link rel="stylesheet" href="{{ prefix }}/_static/css/fbm.css" type="text/css" />
<link rel="stylesheet" href="{{ prefix }}/_static/jquery/css/themes/base/jquery-ui.min.css" >
<script src="{{ prefix }}/_static/jquery/js/jquery-1.11.3.min.js"></script>
<script src="{{ prefix }}/_static/jquery/js/jquery-ui.min.js"></script>
<script src="{{ prefix }}/_static/js/mpl_tornado.js"></script>
<script src="{{ prefix }}/js/mpl.js"></script>
<script>
$(document).ready(
function() {
var websocket_type = mpl.get_websocket_type();
var websocket = new websocket_type(
"{{ ws_uri }}" + {{ repr(str(fig_id)) }} + "/ws");
var fig = new mpl.figure(
{{repr(str(fig_id))}}, websocket, mpl_ondownload, $('div#figure'));
}
);
</script>
<title>matplotlib</title>
</head>
<body>
<div id="mpl-warnings" class="mpl-warnings"></div>
<div id="figure" style="margin: 10px 10px;"></div>
</body>
</html>
@@ -1,31 +0,0 @@
"""
MS Windows-specific helper for the TkAgg backend.
With rcParams['tk.window_focus'] default of False, it is
effectively disabled.
It uses a tiny C++ extension module to access MS Win functions.
This module is deprecated and will be removed in version 3.2
"""
from matplotlib import rcParams, cbook
cbook.warn_deprecated('3.0', obj_type='module', name='backends.windowing')
try:
if not rcParams['tk.window_focus']:
raise ImportError
from matplotlib._windowing import GetForegroundWindow, SetForegroundWindow
except ImportError:
def GetForegroundWindow():
return 0
def SetForegroundWindow(hwnd):
pass
class FocusManager(object):
def __init__(self):
self._shellWindow = GetForegroundWindow()
def __del__(self):
SetForegroundWindow(self._shellWindow)
@@ -1,36 +0,0 @@
"""
A wx API adapter to hide differences between wxPython classic and phoenix.
It is assumed that the user code is selecting what version it wants to use,
here we just ensure that it meets the minimum required by matplotlib.
For an example see embedding_in_wx2.py
"""
import wx
from .. import cbook
from .backend_wx import RendererWx
cbook.warn_deprecated("3.0", "{} is deprecated.".format(__name__))
backend_version = wx.VERSION_STRING
is_phoenix = 'phoenix' in wx.PlatformInfo
fontweights = RendererWx.fontweights
fontangles = RendererWx.fontangles
fontnames = RendererWx.fontnames
dashd_wx = {'solid': wx.PENSTYLE_SOLID,
'dashed': wx.PENSTYLE_SHORT_DASH,
'dashdot': wx.PENSTYLE_DOT_DASH,
'dotted': wx.PENSTYLE_DOT}
# functions changes
BitmapFromBuffer = wx.Bitmap.FromBufferRGBA
EmptyBitmap = wx.Bitmap
EmptyImage = wx.Image
Cursor = wx.Cursor
EventLoop = wx.GUIEventLoop
NamedColour = wx.Colour
StockCursor = wx.Cursor
@@ -1,490 +0,0 @@
"""
A module providing some utility functions regarding bezier path manipulation.
"""
import warnings
import numpy as np
from matplotlib.path import Path
class NonIntersectingPathException(ValueError):
pass
# some functions
def get_intersection(cx1, cy1, cos_t1, sin_t1,
cx2, cy2, cos_t2, sin_t2):
""" return a intersecting point between a line through (cx1, cy1)
and having angle t1 and a line through (cx2, cy2) and angle t2.
"""
# line1 => sin_t1 * (x - cx1) - cos_t1 * (y - cy1) = 0.
# line1 => sin_t1 * x + cos_t1 * y = sin_t1*cx1 - cos_t1*cy1
line1_rhs = sin_t1 * cx1 - cos_t1 * cy1
line2_rhs = sin_t2 * cx2 - cos_t2 * cy2
# rhs matrix
a, b = sin_t1, -cos_t1
c, d = sin_t2, -cos_t2
ad_bc = a * d - b * c
if np.abs(ad_bc) < 1.0e-12:
raise ValueError("Given lines do not intersect. Please verify that "
"the angles are not equal or differ by 180 degrees.")
# rhs_inverse
a_, b_ = d, -b
c_, d_ = -c, a
a_, b_, c_, d_ = [k / ad_bc for k in [a_, b_, c_, d_]]
x = a_ * line1_rhs + b_ * line2_rhs
y = c_ * line1_rhs + d_ * line2_rhs
return x, y
def get_normal_points(cx, cy, cos_t, sin_t, length):
"""
For a line passing through (*cx*, *cy*) and having a angle *t*, return
locations of the two points located along its perpendicular line at the
distance of *length*.
"""
if length == 0.:
return cx, cy, cx, cy
cos_t1, sin_t1 = sin_t, -cos_t
cos_t2, sin_t2 = -sin_t, cos_t
x1, y1 = length * cos_t1 + cx, length * sin_t1 + cy
x2, y2 = length * cos_t2 + cx, length * sin_t2 + cy
return x1, y1, x2, y2
# BEZIER routines
# subdividing bezier curve
# http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-sub.html
def _de_casteljau1(beta, t):
next_beta = beta[:-1] * (1 - t) + beta[1:] * t
return next_beta
def split_de_casteljau(beta, t):
"""split a bezier segment defined by its controlpoints *beta*
into two separate segment divided at *t* and return their control points.
"""
beta = np.asarray(beta)
beta_list = [beta]
while True:
beta = _de_casteljau1(beta, t)
beta_list.append(beta)
if len(beta) == 1:
break
left_beta = [beta[0] for beta in beta_list]
right_beta = [beta[-1] for beta in reversed(beta_list)]
return left_beta, right_beta
# FIXME spelling mistake in the name of the parameter ``tolerence``
def find_bezier_t_intersecting_with_closedpath(bezier_point_at_t,
inside_closedpath,
t0=0., t1=1., tolerence=0.01):
""" Find a parameter t0 and t1 of the given bezier path which
bounds the intersecting points with a provided closed
path(*inside_closedpath*). Search starts from *t0* and *t1* and it
uses a simple bisecting algorithm therefore one of the end point
must be inside the path while the orther doesn't. The search stop
when |t0-t1| gets smaller than the given tolerence.
value for
- bezier_point_at_t : a function which returns x, y coordinates at *t*
- inside_closedpath : return True if the point is inside the path
"""
# inside_closedpath : function
start = bezier_point_at_t(t0)
end = bezier_point_at_t(t1)
start_inside = inside_closedpath(start)
end_inside = inside_closedpath(end)
if start_inside == end_inside and start != end:
raise NonIntersectingPathException(
"Both points are on the same side of the closed path")
while True:
# return if the distance is smaller than the tolerence
if np.hypot(start[0] - end[0], start[1] - end[1]) < tolerence:
return t0, t1
# calculate the middle point
middle_t = 0.5 * (t0 + t1)
middle = bezier_point_at_t(middle_t)
middle_inside = inside_closedpath(middle)
if start_inside ^ middle_inside:
t1 = middle_t
end = middle
end_inside = middle_inside
else:
t0 = middle_t
start = middle
start_inside = middle_inside
class BezierSegment(object):
"""
A simple class of a 2-dimensional bezier segment
"""
# Higher order bezier lines can be supported by simplying adding
# corresponding values.
_binom_coeff = {1: np.array([1., 1.]),
2: np.array([1., 2., 1.]),
3: np.array([1., 3., 3., 1.])}
def __init__(self, control_points):
"""
*control_points* : location of contol points. It needs have a
shpae of n * 2, where n is the order of the bezier line. 1<=
n <= 3 is supported.
"""
_o = len(control_points)
self._orders = np.arange(_o)
_coeff = BezierSegment._binom_coeff[_o - 1]
xx, yy = np.asarray(control_points).T
self._px = xx * _coeff
self._py = yy * _coeff
def point_at_t(self, t):
"evaluate a point at t"
tt = ((1 - t) ** self._orders)[::-1] * t ** self._orders
_x = np.dot(tt, self._px)
_y = np.dot(tt, self._py)
return _x, _y
def split_bezier_intersecting_with_closedpath(bezier,
inside_closedpath,
tolerence=0.01):
"""
bezier : control points of the bezier segment
inside_closedpath : a function which returns true if the point is inside
the path
"""
bz = BezierSegment(bezier)
bezier_point_at_t = bz.point_at_t
t0, t1 = find_bezier_t_intersecting_with_closedpath(bezier_point_at_t,
inside_closedpath,
tolerence=tolerence)
_left, _right = split_de_casteljau(bezier, (t0 + t1) / 2.)
return _left, _right
def find_r_to_boundary_of_closedpath(inside_closedpath, xy,
cos_t, sin_t,
rmin=0., rmax=1., tolerence=0.01):
"""
Find a radius r (centered at *xy*) between *rmin* and *rmax* at
which it intersect with the path.
inside_closedpath : function
cx, cy : center
cos_t, sin_t : cosine and sine for the angle
rmin, rmax :
"""
cx, cy = xy
def _f(r):
return cos_t * r + cx, sin_t * r + cy
find_bezier_t_intersecting_with_closedpath(_f, inside_closedpath,
t0=rmin, t1=rmax,
tolerence=tolerence)
# matplotlib specific
def split_path_inout(path, inside, tolerence=0.01, reorder_inout=False):
""" divide a path into two segment at the point where inside(x, y)
becomes False.
"""
path_iter = path.iter_segments()
ctl_points, command = next(path_iter)
begin_inside = inside(ctl_points[-2:]) # true if begin point is inside
ctl_points_old = ctl_points
concat = np.concatenate
iold = 0
i = 1
for ctl_points, command in path_iter:
iold = i
i += len(ctl_points) // 2
if inside(ctl_points[-2:]) != begin_inside:
bezier_path = concat([ctl_points_old[-2:], ctl_points])
break
ctl_points_old = ctl_points
else:
raise ValueError("The path does not intersect with the patch")
bp = bezier_path.reshape((-1, 2))
left, right = split_bezier_intersecting_with_closedpath(
bp, inside, tolerence)
if len(left) == 2:
codes_left = [Path.LINETO]
codes_right = [Path.MOVETO, Path.LINETO]
elif len(left) == 3:
codes_left = [Path.CURVE3, Path.CURVE3]
codes_right = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
elif len(left) == 4:
codes_left = [Path.CURVE4, Path.CURVE4, Path.CURVE4]
codes_right = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]
else:
raise AssertionError("This should never be reached")
verts_left = left[1:]
verts_right = right[:]
if path.codes is None:
path_in = Path(concat([path.vertices[:i], verts_left]))
path_out = Path(concat([verts_right, path.vertices[i:]]))
else:
path_in = Path(concat([path.vertices[:iold], verts_left]),
concat([path.codes[:iold], codes_left]))
path_out = Path(concat([verts_right, path.vertices[i:]]),
concat([codes_right, path.codes[i:]]))
if reorder_inout and begin_inside is False:
path_in, path_out = path_out, path_in
return path_in, path_out
def inside_circle(cx, cy, r):
r2 = r ** 2
def _f(xy):
x, y = xy
return (x - cx) ** 2 + (y - cy) ** 2 < r2
return _f
# quadratic bezier lines
def get_cos_sin(x0, y0, x1, y1):
dx, dy = x1 - x0, y1 - y0
d = (dx * dx + dy * dy) ** .5
# Account for divide by zero
if d == 0:
return 0.0, 0.0
return dx / d, dy / d
def check_if_parallel(dx1, dy1, dx2, dy2, tolerence=1.e-5):
""" returns
* 1 if two lines are parralel in same direction
* -1 if two lines are parralel in opposite direction
* 0 otherwise
"""
theta1 = np.arctan2(dx1, dy1)
theta2 = np.arctan2(dx2, dy2)
dtheta = np.abs(theta1 - theta2)
if dtheta < tolerence:
return 1
elif np.abs(dtheta - np.pi) < tolerence:
return -1
else:
return False
def get_parallels(bezier2, width):
"""
Given the quadratic bezier control points *bezier2*, returns
control points of quadratic bezier lines roughly parallel to given
one separated by *width*.
"""
# The parallel bezier lines are constructed by following ways.
# c1 and c2 are control points representing the begin and end of the
# bezier line.
# cm is the middle point
c1x, c1y = bezier2[0]
cmx, cmy = bezier2[1]
c2x, c2y = bezier2[2]
parallel_test = check_if_parallel(c1x - cmx, c1y - cmy,
cmx - c2x, cmy - c2y)
if parallel_test == -1:
warnings.warn(
"Lines do not intersect. A straight line is used instead.")
cos_t1, sin_t1 = get_cos_sin(c1x, c1y, c2x, c2y)
cos_t2, sin_t2 = cos_t1, sin_t1
else:
# t1 and t2 is the angle between c1 and cm, cm, c2. They are
# also a angle of the tangential line of the path at c1 and c2
cos_t1, sin_t1 = get_cos_sin(c1x, c1y, cmx, cmy)
cos_t2, sin_t2 = get_cos_sin(cmx, cmy, c2x, c2y)
# find c1_left, c1_right which are located along the lines
# through c1 and perpendicular to the tangential lines of the
# bezier path at a distance of width. Same thing for c2_left and
# c2_right with respect to c2.
c1x_left, c1y_left, c1x_right, c1y_right = (
get_normal_points(c1x, c1y, cos_t1, sin_t1, width)
)
c2x_left, c2y_left, c2x_right, c2y_right = (
get_normal_points(c2x, c2y, cos_t2, sin_t2, width)
)
# find cm_left which is the intersectng point of a line through
# c1_left with angle t1 and a line through c2_left with angle
# t2. Same with cm_right.
if parallel_test != 0:
# a special case for a straight line, i.e., angle between two
# lines are smaller than some (arbitrtay) value.
cmx_left, cmy_left = (
0.5 * (c1x_left + c2x_left), 0.5 * (c1y_left + c2y_left)
)
cmx_right, cmy_right = (
0.5 * (c1x_right + c2x_right), 0.5 * (c1y_right + c2y_right)
)
else:
cmx_left, cmy_left = get_intersection(c1x_left, c1y_left, cos_t1,
sin_t1, c2x_left, c2y_left,
cos_t2, sin_t2)
cmx_right, cmy_right = get_intersection(c1x_right, c1y_right, cos_t1,
sin_t1, c2x_right, c2y_right,
cos_t2, sin_t2)
# the parallel bezier lines are created with control points of
# [c1_left, cm_left, c2_left] and [c1_right, cm_right, c2_right]
path_left = [(c1x_left, c1y_left),
(cmx_left, cmy_left),
(c2x_left, c2y_left)]
path_right = [(c1x_right, c1y_right),
(cmx_right, cmy_right),
(c2x_right, c2y_right)]
return path_left, path_right
def find_control_points(c1x, c1y, mmx, mmy, c2x, c2y):
""" Find control points of the bezier line through c1, mm, c2. We
simply assume that c1, mm, c2 which have parametric value 0, 0.5, and 1.
"""
cmx = .5 * (4 * mmx - (c1x + c2x))
cmy = .5 * (4 * mmy - (c1y + c2y))
return [(c1x, c1y), (cmx, cmy), (c2x, c2y)]
def make_wedged_bezier2(bezier2, width, w1=1., wm=0.5, w2=0.):
"""
Being similar to get_parallels, returns control points of two quadrativ
bezier lines having a width roughly parallel to given one separated by
*width*.
"""
# c1, cm, c2
c1x, c1y = bezier2[0]
cmx, cmy = bezier2[1]
c3x, c3y = bezier2[2]
# t1 and t2 is the angle between c1 and cm, cm, c3.
# They are also a angle of the tangential line of the path at c1 and c3
cos_t1, sin_t1 = get_cos_sin(c1x, c1y, cmx, cmy)
cos_t2, sin_t2 = get_cos_sin(cmx, cmy, c3x, c3y)
# find c1_left, c1_right which are located along the lines
# through c1 and perpendicular to the tangential lines of the
# bezier path at a distance of width. Same thing for c3_left and
# c3_right with respect to c3.
c1x_left, c1y_left, c1x_right, c1y_right = (
get_normal_points(c1x, c1y, cos_t1, sin_t1, width * w1)
)
c3x_left, c3y_left, c3x_right, c3y_right = (
get_normal_points(c3x, c3y, cos_t2, sin_t2, width * w2)
)
# find c12, c23 and c123 which are middle points of c1-cm, cm-c3 and
# c12-c23
c12x, c12y = (c1x + cmx) * .5, (c1y + cmy) * .5
c23x, c23y = (cmx + c3x) * .5, (cmy + c3y) * .5
c123x, c123y = (c12x + c23x) * .5, (c12y + c23y) * .5
# tangential angle of c123 (angle between c12 and c23)
cos_t123, sin_t123 = get_cos_sin(c12x, c12y, c23x, c23y)
c123x_left, c123y_left, c123x_right, c123y_right = (
get_normal_points(c123x, c123y, cos_t123, sin_t123, width * wm)
)
path_left = find_control_points(c1x_left, c1y_left,
c123x_left, c123y_left,
c3x_left, c3y_left)
path_right = find_control_points(c1x_right, c1y_right,
c123x_right, c123y_right,
c3x_right, c3y_right)
return path_left, path_right
def make_path_regular(p):
"""
fill in the codes if None.
"""
c = p.codes
if c is None:
c = np.empty(p.vertices.shape[:1], "i")
c.fill(Path.LINETO)
c[0] = Path.MOVETO
return Path(p.vertices, c)
else:
return p
def concatenate_paths(paths):
"""
concatenate list of paths into a single path.
"""
vertices = []
codes = []
for p in paths:
p = make_path_regular(p)
vertices.append(p.vertices)
codes.append(p.codes)
_path = Path(np.concatenate(vertices),
np.concatenate(codes))
return _path
@@ -1,352 +0,0 @@
"""
This provides several classes used for blocking interaction with figure
windows:
`BlockingInput`
Creates a callable object to retrieve events in a blocking way for
interactive sessions. Base class of the other classes listed here.
`BlockingKeyMouseInput`
Creates a callable object to retrieve key or mouse clicks in a blocking
way for interactive sessions. Used by `waitforbuttonpress`.
`BlockingMouseInput`
Creates a callable object to retrieve mouse clicks in a blocking way for
interactive sessions. Used by `ginput`.
`BlockingContourLabeler`
Creates a callable object to retrieve mouse clicks in a blocking way that
will then be used to place labels on a `ContourSet`. Used by `clabel`.
"""
import logging
from numbers import Integral
import matplotlib.lines as mlines
_log = logging.getLogger(__name__)
class BlockingInput(object):
"""Callable for retrieving events in a blocking way."""
def __init__(self, fig, eventslist=()):
self.fig = fig
self.eventslist = eventslist
def on_event(self, event):
"""
Event handler; will be passed to the current figure to retrieve events.
"""
# Add a new event to list - using a separate function is overkill for
# the base class, but this is consistent with subclasses.
self.add_event(event)
_log.info("Event %i", len(self.events))
# This will extract info from events.
self.post_event()
# Check if we have enough events already.
if len(self.events) >= self.n > 0:
self.fig.canvas.stop_event_loop()
def post_event(self):
"""For baseclass, do nothing but collect events."""
def cleanup(self):
"""Disconnect all callbacks."""
for cb in self.callbacks:
self.fig.canvas.mpl_disconnect(cb)
self.callbacks = []
def add_event(self, event):
"""For base class, this just appends an event to events."""
self.events.append(event)
def pop_event(self, index=-1):
"""
Remove an event from the event list -- by default, the last.
Note that this does not check that there are events, much like the
normal pop method. If no events exist, this will throw an exception.
"""
self.events.pop(index)
pop = pop_event
def __call__(self, n=1, timeout=30):
"""Blocking call to retrieve *n* events."""
if not isinstance(n, Integral):
raise ValueError("Requires an integer argument")
self.n = n
self.events = []
if hasattr(self.fig.canvas, "manager"):
# Ensure that the figure is shown, if we are managing it.
self.fig.show()
# Connect the events to the on_event function call.
self.callbacks = [self.fig.canvas.mpl_connect(name, self.on_event)
for name in self.eventslist]
try:
# Start event loop.
self.fig.canvas.start_event_loop(timeout=timeout)
finally: # Run even on exception like ctrl-c.
# Disconnect the callbacks.
self.cleanup()
# Return the events in this case.
return self.events
class BlockingMouseInput(BlockingInput):
"""
Callable for retrieving mouse clicks in a blocking way.
This class will also retrieve keypresses and map them to mouse clicks:
delete and backspace are like mouse button 3, enter is like mouse button 2
and all others are like mouse button 1.
"""
button_add = 1
button_pop = 3
button_stop = 2
def __init__(self, fig, mouse_add=1, mouse_pop=3, mouse_stop=2):
BlockingInput.__init__(self, fig=fig,
eventslist=('button_press_event',
'key_press_event'))
self.button_add = mouse_add
self.button_pop = mouse_pop
self.button_stop = mouse_stop
def post_event(self):
"""Process an event."""
if len(self.events) == 0:
_log.warning("No events yet")
elif self.events[-1].name == 'key_press_event':
self.key_event()
else:
self.mouse_event()
def mouse_event(self):
"""Process a mouse click event."""
event = self.events[-1]
button = event.button
if button == self.button_pop:
self.mouse_event_pop(event)
elif button == self.button_stop:
self.mouse_event_stop(event)
else:
self.mouse_event_add(event)
def key_event(self):
"""
Process a key press event, mapping keys to appropriate mouse clicks.
"""
event = self.events[-1]
if event.key is None:
# At least in OSX gtk backend some keys return None.
return
key = event.key.lower()
if key in ['backspace', 'delete']:
self.mouse_event_pop(event)
elif key in ['escape', 'enter']:
self.mouse_event_stop(event)
else:
self.mouse_event_add(event)
def mouse_event_add(self, event):
"""
Process an button-1 event (add a click if inside axes).
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
if event.inaxes:
self.add_click(event)
else: # If not a valid click, remove from event list.
BlockingInput.pop(self)
def mouse_event_stop(self, event):
"""
Process an button-2 event (end blocking input).
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
# Remove last event just for cleanliness.
BlockingInput.pop(self)
# This will exit even if not in infinite mode. This is consistent with
# MATLAB and sometimes quite useful, but will require the user to test
# how many points were actually returned before using data.
self.fig.canvas.stop_event_loop()
def mouse_event_pop(self, event):
"""
Process an button-3 event (remove the last click).
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
# Remove this last event.
BlockingInput.pop(self)
# Now remove any existing clicks if possible.
if self.events:
self.pop(event)
def add_click(self, event):
"""
Add the coordinates of an event to the list of clicks.
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
self.clicks.append((event.xdata, event.ydata))
_log.info("input %i: %f, %f",
len(self.clicks), event.xdata, event.ydata)
# If desired, plot up click.
if self.show_clicks:
line = mlines.Line2D([event.xdata], [event.ydata],
marker='+', color='r')
event.inaxes.add_line(line)
self.marks.append(line)
self.fig.canvas.draw()
def pop_click(self, event, index=-1):
"""
Remove a click (by default, the last) from the list of clicks.
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
self.clicks.pop(index)
if self.show_clicks:
self.marks.pop(index).remove()
self.fig.canvas.draw()
def pop(self, event, index=-1):
"""
Removes a click and the associated event from the list of clicks.
Defaults to the last click.
"""
self.pop_click(event, index)
BlockingInput.pop(self, index)
def cleanup(self, event=None):
"""
Parameters
----------
event : `~.backend_bases.MouseEvent`, optional
Not used
"""
# Clean the figure.
if self.show_clicks:
for mark in self.marks:
mark.remove()
self.marks = []
self.fig.canvas.draw()
# Call base class to remove callbacks.
BlockingInput.cleanup(self)
def __call__(self, n=1, timeout=30, show_clicks=True):
"""
Blocking call to retrieve *n* coordinate pairs through mouse clicks.
"""
self.show_clicks = show_clicks
self.clicks = []
self.marks = []
BlockingInput.__call__(self, n=n, timeout=timeout)
return self.clicks
class BlockingContourLabeler(BlockingMouseInput):
"""
Callable for retrieving mouse clicks and key presses in a blocking way.
Used to place contour labels.
"""
def __init__(self, cs):
self.cs = cs
BlockingMouseInput.__init__(self, fig=cs.ax.figure)
def add_click(self, event):
self.button1(event)
def pop_click(self, event, index=-1):
self.button3(event)
def button1(self, event):
"""
Process an button-1 event (add a label to a contour).
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
# Shorthand
if event.inaxes == self.cs.ax:
self.cs.add_label_near(event.x, event.y, self.inline,
inline_spacing=self.inline_spacing,
transform=False)
self.fig.canvas.draw()
else: # Remove event if not valid
BlockingInput.pop(self)
def button3(self, event):
"""
Process an button-3 event (remove a label if not in inline mode).
Unfortunately, if one is doing inline labels, then there is currently
no way to fix the broken contour - once humpty-dumpty is broken, he
can't be put back together. In inline mode, this does nothing.
Parameters
----------
event : `~.backend_bases.MouseEvent`
"""
if self.inline:
pass
else:
self.cs.pop_label()
self.cs.ax.figure.canvas.draw()
def __call__(self, inline, inline_spacing=5, n=-1, timeout=-1):
self.inline = inline
self.inline_spacing = inline_spacing
BlockingMouseInput.__call__(self, n=n, timeout=timeout,
show_clicks=False)
class BlockingKeyMouseInput(BlockingInput):
"""
Callable for retrieving mouse clicks and key presses in a blocking way.
"""
def __init__(self, fig):
BlockingInput.__init__(self, fig=fig, eventslist=(
'button_press_event', 'key_press_event'))
def post_event(self):
"""Determine if it is a key event."""
if self.events:
self.keyormouse = self.events[-1].name == 'key_press_event'
else:
_log.warning("No events yet.")
def __call__(self, timeout=30):
"""
Blocking call to retrieve a single mouse click or key press.
Returns ``True`` if key press, ``False`` if mouse click, or ``None`` if
timed out.
"""
self.keyormouse = None
BlockingInput.__call__(self, n=1, timeout=timeout)
return self.keyormouse
@@ -1,197 +0,0 @@
"""
Module that allows plotting of string "category" data. i.e.
``plot(['d', 'f', 'a'],[1, 2, 3])`` will plot three points with x-axis
values of 'd', 'f', 'a'.
See :doc:`/gallery/lines_bars_and_markers/categorical_variables` for an
example.
The module uses Matplotlib's `matplotlib.units` mechanism to convert from
strings to integers, provides a tick locator and formatter, and the
class:`.UnitData` that creates and stores the string-to-integer mapping.
"""
from collections import OrderedDict
import itertools
import numpy as np
import matplotlib.units as units
import matplotlib.ticker as ticker
class StrCategoryConverter(units.ConversionInterface):
@staticmethod
def convert(value, unit, axis):
"""Converts strings in value to floats using
mapping information store in the unit object.
Parameters
----------
value : string or iterable
value or list of values to be converted
unit : :class:`.UnitData`
object string unit information for value
axis : :class:`~matplotlib.Axis.axis`
axis on which the converted value is plotted
Returns
-------
mapped_ value : float or ndarray[float]
.. note:: axis is not used in this function
"""
# dtype = object preserves numerical pass throughs
values = np.atleast_1d(np.array(value, dtype=object))
# pass through sequence of non binary numbers
if all((units.ConversionInterface.is_numlike(v) and
not isinstance(v, (str, bytes))) for v in values):
return np.asarray(values, dtype=float)
# force an update so it also does type checking
unit.update(values)
str2idx = np.vectorize(unit._mapping.__getitem__,
otypes=[float])
mapped_value = str2idx(values)
return mapped_value
@staticmethod
def axisinfo(unit, axis):
"""Sets the default axis ticks and labels
Parameters
---------
unit : :class:`.UnitData`
object string unit information for value
axis : :class:`~matplotlib.Axis.axis`
axis for which information is being set
Returns
-------
:class:~matplotlib.units.AxisInfo~
Information to support default tick labeling
.. note: axis is not used
"""
# locator and formatter take mapping dict because
# args need to be pass by reference for updates
majloc = StrCategoryLocator(unit._mapping)
majfmt = StrCategoryFormatter(unit._mapping)
return units.AxisInfo(majloc=majloc, majfmt=majfmt)
@staticmethod
def default_units(data, axis):
"""Sets and updates the :class:`~matplotlib.Axis.axis` units.
Parameters
----------
data : string or iterable of strings
axis : :class:`~matplotlib.Axis.axis`
axis on which the data is plotted
Returns
-------
class:~.UnitData~
object storing string to integer mapping
"""
# the conversion call stack is supposed to be
# default_units->axis_info->convert
if axis.units is None:
axis.set_units(UnitData(data))
else:
axis.units.update(data)
return axis.units
class StrCategoryLocator(ticker.Locator):
"""tick at every integer mapping of the string data"""
def __init__(self, units_mapping):
"""
Parameters
-----------
units_mapping : Dict[str, int]
string:integer mapping
"""
self._units = units_mapping
def __call__(self):
return list(self._units.values())
def tick_values(self, vmin, vmax):
return self()
class StrCategoryFormatter(ticker.Formatter):
"""String representation of the data at every tick"""
def __init__(self, units_mapping):
"""
Parameters
----------
units_mapping : Dict[Str, int]
string:integer mapping
"""
self._units = units_mapping
def __call__(self, x, pos=None):
if pos is None:
return ""
r_mapping = {v: StrCategoryFormatter._text(k)
for k, v in self._units.items()}
return r_mapping.get(int(np.round(x)), '')
@staticmethod
def _text(value):
"""Converts text values into utf-8 or ascii strings.
"""
if isinstance(value, bytes):
value = value.decode(encoding='utf-8')
elif not isinstance(value, str):
value = str(value)
return value
class UnitData(object):
def __init__(self, data=None):
"""
Create mapping between unique categorical values and integer ids.
Parameters
----------
data: iterable
sequence of string values
"""
self._mapping = OrderedDict()
self._counter = itertools.count()
if data is not None:
self.update(data)
def update(self, data):
"""Maps new values to integer identifiers.
Parameters
----------
data: iterable
sequence of string values
Raises
------
TypeError
If the value in data is not a string, unicode, bytes type
"""
data = np.atleast_1d(np.array(data, dtype=object))
for val in OrderedDict.fromkeys(data):
if not isinstance(val, (str, bytes)):
raise TypeError("{val!r} is not a string".format(val=val))
if val not in self._mapping:
self._mapping[val] = next(self._counter)
# Connects the convertor to matplotlib
units.registry[str] = StrCategoryConverter()
units.registry[np.str_] = StrCategoryConverter()
units.registry[bytes] = StrCategoryConverter()
units.registry[np.bytes_] = StrCategoryConverter()
File diff suppressed because it is too large Load Diff
@@ -1,264 +0,0 @@
import functools
import textwrap
import warnings
class MatplotlibDeprecationWarning(UserWarning):
"""
A class for issuing deprecation warnings for Matplotlib users.
In light of the fact that Python builtin DeprecationWarnings are ignored
by default as of Python 2.7 (see link below), this class was put in to
allow for the signaling of deprecation, but via UserWarnings which are not
ignored by default.
https://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x
"""
mplDeprecation = MatplotlibDeprecationWarning
"""mplDeprecation is deprecated. Use MatplotlibDeprecationWarning instead."""
def _generate_deprecation_message(
since, message='', name='', alternative='', pending=False,
obj_type='attribute', addendum='', *, removal=''):
if removal == "":
removal = {"2.2": "in 3.1", "3.0": "in 3.2"}.get(
since, "two minor releases later")
elif removal:
if pending:
raise ValueError(
"A pending deprecation cannot have a scheduled removal")
removal = "in {}".format(removal)
if not message:
message = (
"The %(name)s %(obj_type)s"
+ (" will be deprecated in a future version"
if pending else
(" was deprecated in Matplotlib %(since)s"
+ (" and will be removed %(removal)s"
if removal else
"")))
+ "."
+ (" Use %(alternative)s instead." if alternative else "")
+ (" %(addendum)s" if addendum else ""))
return message % dict(
func=name, name=name, obj_type=obj_type, since=since, removal=removal,
alternative=alternative, addendum=addendum)
def warn_deprecated(
since, message='', name='', alternative='', pending=False,
obj_type='attribute', addendum='', *, removal=''):
"""
Used to display deprecation in a standard way.
Parameters
----------
since : str
The release at which this API became deprecated.
message : str, optional
Override the default deprecation message. The format
specifier `%(name)s` may be used for the name of the function,
and `%(alternative)s` may be used in the deprecation message
to insert the name of an alternative to the deprecated
function. `%(obj_type)s` may be used to insert a friendly name
for the type of object being deprecated.
name : str, optional
The name of the deprecated object.
alternative : str, optional
An alternative API that the user may use in place of the deprecated
API. The deprecation warning will tell the user about this alternative
if provided.
pending : bool, optional
If True, uses a PendingDeprecationWarning instead of a
DeprecationWarning. Cannot be used together with *removal*.
removal : str, optional
The expected removal version. With the default (an empty string), a
removal version is automatically computed from *since*. Set to other
Falsy values to not schedule a removal date. Cannot be used together
with *pending*.
obj_type : str, optional
The object type being deprecated.
addendum : str, optional
Additional text appended directly to the final message.
Examples
--------
Basic example::
# To warn of the deprecation of "matplotlib.name_of_module"
warn_deprecated('1.4.0', name='matplotlib.name_of_module',
obj_type='module')
"""
message = '\n' + _generate_deprecation_message(
since, message, name, alternative, pending, obj_type, addendum,
removal=removal)
category = (PendingDeprecationWarning if pending
else MatplotlibDeprecationWarning)
warnings.warn(message, category, stacklevel=2)
def deprecated(since, message='', name='', alternative='', pending=False,
obj_type=None, addendum='', *, removal=''):
"""
Decorator to mark a function or a class as deprecated.
Parameters
----------
since : str
The release at which this API became deprecated. This is
required.
message : str, optional
Override the default deprecation message. The format
specifier `%(name)s` may be used for the name of the object,
and `%(alternative)s` may be used in the deprecation message
to insert the name of an alternative to the deprecated
object.
name : str, optional
The name of the deprecated object; if not provided the name
is automatically determined from the passed in object,
though this is useful in the case of renamed functions, where
the new function is just assigned to the name of the
deprecated function. For example::
def new_function():
...
oldFunction = new_function
alternative : str, optional
An alternative API that the user may use in place of the deprecated
API. The deprecation warning will tell the user about this alternative
if provided.
pending : bool, optional
If True, uses a PendingDeprecationWarning instead of a
DeprecationWarning. Cannot be used together with *removal*.
removal : str, optional
The expected removal version. With the default (an empty string), a
removal version is automatically computed from *since*. Set to other
Falsy values to not schedule a removal date. Cannot be used together
with *pending*.
addendum : str, optional
Additional text appended directly to the final message.
Examples
--------
Basic example::
@deprecated('1.4.0')
def the_function_to_deprecate():
pass
"""
if obj_type is not None:
warn_deprecated(
"3.0", "Passing 'obj_type' to the 'deprecated' decorator has no "
"effect, and is deprecated since Matplotlib %(since)s; support "
"for it will be removed %(removal)s.")
def deprecate(obj, message=message, name=name, alternative=alternative,
pending=pending, addendum=addendum):
if not name:
name = obj.__name__
if isinstance(obj, type):
obj_type = "class"
old_doc = obj.__doc__
func = obj.__init__
def finalize(wrapper, new_doc):
obj.__doc__ = new_doc
obj.__init__ = wrapper
return obj
elif isinstance(obj, property):
obj_type = "attribute"
func = None
name = name or obj.fget.__name__
old_doc = obj.__doc__
class _deprecated_property(property):
def __get__(self, instance, owner):
if instance is not None:
from . import _warn_external
_warn_external(message, category)
return super().__get__(instance, owner)
def __set__(self, instance, value):
if instance is not None:
from . import _warn_external
_warn_external(message, category)
return super().__set__(instance, value)
def __delete__(self, instance):
if instance is not None:
from . import _warn_external
_warn_external(message, category)
return super().__delete__(instance)
def finalize(_, new_doc):
return _deprecated_property(
fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc)
else:
obj_type = "function"
if isinstance(obj, classmethod):
func = obj.__func__
old_doc = func.__doc__
def finalize(wrapper, new_doc):
wrapper = functools.wraps(func)(wrapper)
wrapper.__doc__ = new_doc
return classmethod(wrapper)
else:
func = obj
old_doc = func.__doc__
def finalize(wrapper, new_doc):
wrapper = functools.wraps(func)(wrapper)
wrapper.__doc__ = new_doc
return wrapper
message = _generate_deprecation_message(
since, message, name, alternative, pending, obj_type, addendum,
removal=removal)
category = (PendingDeprecationWarning if pending
else MatplotlibDeprecationWarning)
def wrapper(*args, **kwargs):
warnings.warn(message, category, stacklevel=2)
return func(*args, **kwargs)
old_doc = textwrap.dedent(old_doc or '').strip('\n')
message = message.strip()
new_doc = (('\n.. deprecated:: %(since)s'
'\n %(message)s\n\n' %
{'since': since, 'message': message}) + old_doc)
if not old_doc:
# This is to prevent a spurious 'unexected unindent' warning from
# docutils when the original docstring was blank.
new_doc += r'\ '
return finalize(wrapper, new_doc)
return deprecate
@@ -1,404 +0,0 @@
"""
Builtin colormaps, colormap handling utilities, and the `ScalarMappable` mixin.
.. seealso::
:doc:`/gallery/color/colormap_reference` for a list of builtin
colormaps.
:doc:`/tutorials/colors/colormap-manipulation` for examples of how to
make colormaps and
:doc:`/tutorials/colors/colormaps` an in-depth discussion of
choosing colormaps.
:doc:`/tutorials/colors/colormapnorms` for more details about data
normalization
"""
import functools
import numpy as np
from numpy import ma
import matplotlib as mpl
import matplotlib.colors as colors
import matplotlib.cbook as cbook
from matplotlib._cm import datad
from matplotlib._cm_listed import cmaps as cmaps_listed
cmap_d = {}
# reverse all the colormaps.
# reversed colormaps have '_r' appended to the name.
def _reverser(f, x=None):
"""Helper such that ``_reverser(f)(x) == f(1 - x)``."""
if x is None:
# Returning a partial object keeps it picklable.
return functools.partial(_reverser, f)
return f(1 - x)
def revcmap(data):
"""Can only handle specification *data* in dictionary format."""
data_r = {}
for key, val in data.items():
if callable(val):
valnew = _reverser(val)
# This doesn't work: lambda x: val(1-x)
# The same "val" (the first one) is used
# each time, so the colors are identical
# and the result is shades of gray.
else:
# Flip x and exchange the y values facing x = 0 and x = 1.
valnew = [(1.0 - x, y1, y0) for x, y0, y1 in reversed(val)]
data_r[key] = valnew
return data_r
def _reverse_cmap_spec(spec):
"""Reverses cmap specification *spec*, can handle both dict and tuple
type specs."""
if 'listed' in spec:
return {'listed': spec['listed'][::-1]}
if 'red' in spec:
return revcmap(spec)
else:
revspec = list(reversed(spec))
if len(revspec[0]) == 2: # e.g., (1, (1.0, 0.0, 1.0))
revspec = [(1.0 - a, b) for a, b in revspec]
return revspec
def _generate_cmap(name, lutsize):
"""Generates the requested cmap from its *name*. The lut size is
*lutsize*."""
spec = datad[name]
# Generate the colormap object.
if 'red' in spec:
return colors.LinearSegmentedColormap(name, spec, lutsize)
elif 'listed' in spec:
return colors.ListedColormap(spec['listed'], name)
else:
return colors.LinearSegmentedColormap.from_list(name, spec, lutsize)
LUTSIZE = mpl.rcParams['image.lut']
# Generate the reversed specifications (all at once, to avoid
# modify-when-iterating).
datad.update({cmapname + '_r': _reverse_cmap_spec(spec)
for cmapname, spec in datad.items()})
# Precache the cmaps with ``lutsize = LUTSIZE``.
# Also add the reversed ones added in the section above:
for cmapname in datad:
cmap_d[cmapname] = _generate_cmap(cmapname, LUTSIZE)
cmap_d.update(cmaps_listed)
locals().update(cmap_d)
# Continue with definitions ...
def register_cmap(name=None, cmap=None, data=None, lut=None):
"""
Add a colormap to the set recognized by :func:`get_cmap`.
It can be used in two ways::
register_cmap(name='swirly', cmap=swirly_cmap)
register_cmap(name='choppy', data=choppydata, lut=128)
In the first case, *cmap* must be a :class:`matplotlib.colors.Colormap`
instance. The *name* is optional; if absent, the name will
be the :attr:`~matplotlib.colors.Colormap.name` attribute of the *cmap*.
In the second case, the three arguments are passed to
the :class:`~matplotlib.colors.LinearSegmentedColormap` initializer,
and the resulting colormap is registered.
"""
if name is None:
try:
name = cmap.name
except AttributeError:
raise ValueError("Arguments must include a name or a Colormap")
if not isinstance(name, str):
raise ValueError("Colormap name must be a string")
if isinstance(cmap, colors.Colormap):
cmap_d[name] = cmap
return
# For the remainder, let exceptions propagate.
if lut is None:
lut = mpl.rcParams['image.lut']
cmap = colors.LinearSegmentedColormap(name, data, lut)
cmap_d[name] = cmap
def get_cmap(name=None, lut=None):
"""
Get a colormap instance, defaulting to rc values if *name* is None.
Colormaps added with :func:`register_cmap` take precedence over
built-in colormaps.
If *name* is a :class:`matplotlib.colors.Colormap` instance, it will be
returned.
If *lut* is not None it must be an integer giving the number of
entries desired in the lookup table, and *name* must be a standard
mpl colormap name.
"""
if name is None:
name = mpl.rcParams['image.cmap']
if isinstance(name, colors.Colormap):
return name
if name in cmap_d:
if lut is None:
return cmap_d[name]
else:
return cmap_d[name]._resample(lut)
else:
raise ValueError(
"Colormap %s is not recognized. Possible values are: %s"
% (name, ', '.join(sorted(cmap_d))))
class ScalarMappable(object):
"""
This is a mixin class to support scalar data to RGBA mapping.
The ScalarMappable makes use of data normalization before returning
RGBA colors from the given colormap.
"""
def __init__(self, norm=None, cmap=None):
r"""
Parameters
----------
norm : :class:`matplotlib.colors.Normalize` instance
The normalizing object which scales data, typically into the
interval ``[0, 1]``.
If *None*, *norm* defaults to a *colors.Normalize* object which
initializes its scaling based on the first data processed.
cmap : str or :class:`~matplotlib.colors.Colormap` instance
The colormap used to map normalized data values to RGBA colors.
"""
self.callbacksSM = cbook.CallbackRegistry()
if cmap is None:
cmap = get_cmap()
if norm is None:
norm = colors.Normalize()
self._A = None
#: The Normalization instance of this ScalarMappable.
self.norm = norm
#: The Colormap instance of this ScalarMappable.
self.cmap = get_cmap(cmap)
#: The last colorbar associated with this ScalarMappable. May be None.
self.colorbar = None
self.update_dict = {'array': False}
def to_rgba(self, x, alpha=None, bytes=False, norm=True):
"""
Return a normalized rgba array corresponding to *x*.
In the normal case, *x* is a 1-D or 2-D sequence of scalars, and
the corresponding ndarray of rgba values will be returned,
based on the norm and colormap set for this ScalarMappable.
There is one special case, for handling images that are already
rgb or rgba, such as might have been read from an image file.
If *x* is an ndarray with 3 dimensions,
and the last dimension is either 3 or 4, then it will be
treated as an rgb or rgba array, and no mapping will be done.
The array can be uint8, or it can be floating point with
values in the 0-1 range; otherwise a ValueError will be raised.
If it is a masked array, the mask will be ignored.
If the last dimension is 3, the *alpha* kwarg (defaulting to 1)
will be used to fill in the transparency. If the last dimension
is 4, the *alpha* kwarg is ignored; it does not
replace the pre-existing alpha. A ValueError will be raised
if the third dimension is other than 3 or 4.
In either case, if *bytes* is *False* (default), the rgba
array will be floats in the 0-1 range; if it is *True*,
the returned rgba array will be uint8 in the 0 to 255 range.
If norm is False, no normalization of the input data is
performed, and it is assumed to be in the range (0-1).
"""
# First check for special case, image input:
try:
if x.ndim == 3:
if x.shape[2] == 3:
if alpha is None:
alpha = 1
if x.dtype == np.uint8:
alpha = np.uint8(alpha * 255)
m, n = x.shape[:2]
xx = np.empty(shape=(m, n, 4), dtype=x.dtype)
xx[:, :, :3] = x
xx[:, :, 3] = alpha
elif x.shape[2] == 4:
xx = x
else:
raise ValueError("third dimension must be 3 or 4")
if xx.dtype.kind == 'f':
if norm and (xx.max() > 1 or xx.min() < 0):
raise ValueError("Floating point image RGB values "
"must be in the 0..1 range.")
if bytes:
xx = (xx * 255).astype(np.uint8)
elif xx.dtype == np.uint8:
if not bytes:
xx = xx.astype(np.float32) / 255
else:
raise ValueError("Image RGB array must be uint8 or "
"floating point; found %s" % xx.dtype)
return xx
except AttributeError:
# e.g., x is not an ndarray; so try mapping it
pass
# This is the normal case, mapping a scalar array:
x = ma.asarray(x)
if norm:
x = self.norm(x)
rgba = self.cmap(x, alpha=alpha, bytes=bytes)
return rgba
def set_array(self, A):
"""Set the image array from numpy array *A*.
Parameters
----------
A : ndarray
"""
self._A = A
self.update_dict['array'] = True
def get_array(self):
'Return the array'
return self._A
def get_cmap(self):
'return the colormap'
return self.cmap
def get_clim(self):
'return the min, max of the color limits for image scaling'
return self.norm.vmin, self.norm.vmax
def set_clim(self, vmin=None, vmax=None):
"""
set the norm limits for image scaling; if *vmin* is a length2
sequence, interpret it as ``(vmin, vmax)`` which is used to
support setp
ACCEPTS: a length 2 sequence of floats; may be overridden in methods
that have ``vmin`` and ``vmax`` kwargs.
"""
if vmax is None:
try:
vmin, vmax = vmin
except (TypeError, ValueError):
pass
if vmin is not None:
self.norm.vmin = colors._sanitize_extrema(vmin)
if vmax is not None:
self.norm.vmax = colors._sanitize_extrema(vmax)
self.changed()
def set_cmap(self, cmap):
"""
set the colormap for luminance data
Parameters
----------
cmap : colormap or registered colormap name
"""
cmap = get_cmap(cmap)
self.cmap = cmap
self.changed()
def set_norm(self, norm):
"""Set the normalization instance.
Parameters
----------
norm : `.Normalize`
"""
if norm is None:
norm = colors.Normalize()
self.norm = norm
self.changed()
def autoscale(self):
"""
Autoscale the scalar limits on the norm instance using the
current array
"""
if self._A is None:
raise TypeError('You must first set_array for mappable')
self.norm.autoscale(self._A)
self.changed()
def autoscale_None(self):
"""
Autoscale the scalar limits on the norm instance using the
current array, changing only limits that are None
"""
if self._A is None:
raise TypeError('You must first set_array for mappable')
self.norm.autoscale_None(self._A)
self.changed()
def add_checker(self, checker):
"""
Add an entry to a dictionary of boolean flags
that are set to True when the mappable is changed.
"""
self.update_dict[checker] = False
def check_update(self, checker):
"""
If mappable has changed since the last check,
return True; else return False
"""
if self.update_dict[checker]:
self.update_dict[checker] = False
return True
return False
def changed(self):
"""
Call this whenever the mappable is changed to notify all the
callbackSM listeners to the 'changed' signal
"""
self.callbacksSM.process('changed', self)
for key in self.update_dict:
self.update_dict[key] = True
self.stale = True
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More