Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

828 lines (689 sloc) 31.274 kb
from __future__ import division, print_function
import math
import os
import signal
import sys
import matplotlib
from matplotlib import verbose
from matplotlib.cbook import is_string_like, onetrue
from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \
FigureManagerBase, FigureCanvasBase, NavigationToolbar2, IdleEvent, \
cursors, TimerBase
from matplotlib.backend_bases import ShowBase
from matplotlib._pylab_helpers import Gcf
from matplotlib.figure import Figure
from matplotlib.mathtext import MathTextParser
from matplotlib.widgets import SubplotTool
import matplotlib.backends.qt4_editor.figureoptions as figureoptions
except ImportError:
figureoptions = None
from qt4_compat import QtCore, QtGui, _getSaveFileName, __version__
backend_version = __version__
def fn_name(): return sys._getframe(1).f_code.co_name
DEBUG = False
cursord = {
cursors.MOVE : QtCore.Qt.SizeAllCursor,
cursors.HAND : QtCore.Qt.PointingHandCursor,
cursors.POINTER : QtCore.Qt.ArrowCursor,
cursors.SELECT_REGION : QtCore.Qt.CrossCursor,
def draw_if_interactive():
Is called after every pylab drawing command
if matplotlib.is_interactive():
figManager = Gcf.get_active()
if figManager != None:
def _create_qApp():
Only one qApp can exist at a time, so check before creating one.
if QtGui.QApplication.startingUp():
if DEBUG: print("Starting up QApplication")
global qApp
app = QtGui.QApplication.instance()
if app is None:
qApp = QtGui.QApplication( [" "] )
QtCore.QObject.connect( qApp, QtCore.SIGNAL( "lastWindowClosed()" ),
qApp, QtCore.SLOT( "quit()" ) )
qApp = app
class Show(ShowBase):
def mainloop(self):
# allow KeyboardInterrupt exceptions to close the plot window.
signal.signal(signal.SIGINT, signal.SIG_DFL)
show = Show()
def new_figure_manager( num, *args, **kwargs ):
Create a new figure manager instance
thisFig = Figure(*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 = FigureCanvasQT(figure)
manager = FigureManagerQT(canvas, num)
return manager
class TimerQT(TimerBase):
Subclass of :class:`backend_bases.TimerBase` that uses Qt4 timer events.
* interval: The time between timer events in milliseconds. Default
is 1000 ms.
* single_shot: Boolean flag indicating whether this timer should
operate as single shot (run once and then stop). Defaults to False.
* callbacks: 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 __init__(self, *args, **kwargs):
TimerBase.__init__(self, *args, **kwargs)
# Create a new timer and connect the timeout() signal to the
# _on_timer method.
self._timer = QtCore.QTimer()
QtCore.QObject.connect(self._timer, QtCore.SIGNAL('timeout()'),
def __del__(self):
# Probably not necessary in practice, but is good behavior to disconnect
QtCore.SIGNAL('timeout()'), self._on_timer)
except RuntimeError:
# Timer C++ object already deleted
def _timer_set_single_shot(self):
def _timer_set_interval(self):
def _timer_start(self):
def _timer_stop(self):
class FigureCanvasQT( QtGui.QWidget, FigureCanvasBase ):
keyvald = { QtCore.Qt.Key_Control : 'control',
QtCore.Qt.Key_Shift : 'shift',
QtCore.Qt.Key_Alt : 'alt',
QtCore.Qt.Key_Meta : 'super',
QtCore.Qt.Key_Return : 'enter',
QtCore.Qt.Key_Left : 'left',
QtCore.Qt.Key_Up : 'up',
QtCore.Qt.Key_Right : 'right',
QtCore.Qt.Key_Down : 'down',
QtCore.Qt.Key_Escape : 'escape',
QtCore.Qt.Key_F1 : 'f1',
QtCore.Qt.Key_F2 : 'f2',
QtCore.Qt.Key_F3 : 'f3',
QtCore.Qt.Key_F4 : 'f4',
QtCore.Qt.Key_F5 : 'f5',
QtCore.Qt.Key_F6 : 'f6',
QtCore.Qt.Key_F7 : 'f7',
QtCore.Qt.Key_F8 : 'f8',
QtCore.Qt.Key_F9 : 'f9',
QtCore.Qt.Key_F10 : 'f10',
QtCore.Qt.Key_F11 : 'f11',
QtCore.Qt.Key_F12 : 'f12',
QtCore.Qt.Key_Home : 'home',
QtCore.Qt.Key_End : 'end',
QtCore.Qt.Key_PageUp : 'pageup',
QtCore.Qt.Key_PageDown : 'pagedown',
# define the modifier keys which are to be collected on keyboard events.
# format is: [(modifier_flag, modifier_name, equivalent_key)
_modifier_keys = [
(QtCore.Qt.MetaModifier, 'super', QtCore.Qt.Key_Meta),
(QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
(QtCore.Qt.ControlModifier, 'ctrl', QtCore.Qt.Key_Control)
_ctrl_modifier = QtCore.Qt.ControlModifier
if sys.platform == 'darwin':
# in OSX, the control and super (aka cmd/apple) keys are switched, so
# switch them back.
QtCore.Qt.Key_Control : 'super', # cmd/apple key
QtCore.Qt.Key_Meta : 'control',
_modifier_keys = [
(QtCore.Qt.ControlModifier, 'super', QtCore.Qt.Key_Control),
(QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
(QtCore.Qt.MetaModifier, 'ctrl', QtCore.Qt.Key_Meta),
_ctrl_modifier = QtCore.Qt.MetaModifier
# map Qt button codes to MouseEvent's ones:
buttond = {QtCore.Qt.LeftButton : 1,
QtCore.Qt.MidButton : 2,
QtCore.Qt.RightButton : 3,
# QtCore.Qt.XButton1 : None,
# QtCore.Qt.XButton2 : None,
def __init__( self, figure ):
if DEBUG: print('FigureCanvasQt: ', figure)
QtGui.QWidget.__init__( self )
FigureCanvasBase.__init__( self, figure )
self.figure = figure
self.setMouseTracking( True )
self._idle = True
# hide until we can test and fix
w,h = self.get_width_height()
self.resize( w, h )
# JDH: Note the commented out code below does not work as
# expected, because according to Pierre Raybaut, The reason is
# that PyQt fails (silently) to call a method of this object
# just before detroying it. Using a lambda function will work,
# exactly the same as using a function (which is not bound to
# the object to be destroyed).
#QtCore.QObject.connect(self, QtCore.SIGNAL('destroyed()'),
# self.close_event)
QtCore.QObject.connect(self, QtCore.SIGNAL('destroyed()'),
lambda: self.close_event())
def __timerEvent(self, event):
# hide until we can test and fix
def enterEvent(self, event):
FigureCanvasBase.enter_notify_event(self, event)
def leaveEvent(self, event):
FigureCanvasBase.leave_notify_event(self, event)
def mousePressEvent( self, event ):
x = event.pos().x()
# flipy so y=0 is bottom of canvas
y = self.figure.bbox.height - event.pos().y()
button = self.buttond.get(event.button())
if button is not None:
FigureCanvasBase.button_press_event( self, x, y, button )
if DEBUG: print('button pressed:', event.button())
def mouseDoubleClickEvent(self, event):
x = event.pos().x()
# flipy so y=0 is bottom of canvas
y = self.figure.bbox.height - event.pos().y()
button = self.buttond.get(event.button())
if button is not None:
FigureCanvasBase.button_press_event( self, x, y, button, dblclick=True )
if DEBUG: print ('button doubleclicked:', event.button())
def mouseMoveEvent( self, event ):
x = event.x()
# flipy so y=0 is bottom of canvas
y = self.figure.bbox.height - event.y()
FigureCanvasBase.motion_notify_event( self, x, y )
#if DEBUG: print('mouse move')
def mouseReleaseEvent( self, event ):
x = event.x()
# flipy so y=0 is bottom of canvas
y = self.figure.bbox.height - event.y()
button = self.buttond.get(event.button())
if button is not None:
FigureCanvasBase.button_release_event( self, x, y, button )
if DEBUG: print('button released')
def wheelEvent( self, event ):
x = event.x()
# flipy so y=0 is bottom of canvas
y = self.figure.bbox.height - event.y()
# from QWheelEvent::delta doc
steps =
if (event.orientation() == QtCore.Qt.Vertical):
FigureCanvasBase.scroll_event( self, x, y, steps)
if DEBUG: print('scroll event : delta = %i, steps = %i ' % (,steps))
def keyPressEvent( self, event ):
key = self._get_key( event )
if key is None:
FigureCanvasBase.key_press_event( self, key )
if DEBUG: print('key press', key)
def keyReleaseEvent( self, event ):
key = self._get_key(event)
if key is None:
FigureCanvasBase.key_release_event( self, key )
if DEBUG: print('key release', key)
def resizeEvent( self, event ):
if DEBUG: print('resize (%d x %d)' % (event.size().width(), event.size().height()))
w = event.size().width()
h = event.size().height()
if DEBUG: print("FigureCanvasQtAgg.resizeEvent(", w, ",", h, ")")
dpival = self.figure.dpi
winch = w/dpival
hinch = h/dpival
self.figure.set_size_inches( winch, hinch )
QtGui.QWidget.resizeEvent(self, event)
def sizeHint( self ):
w, h = self.get_width_height()
return QtCore.QSize( w, h )
def minumumSizeHint( self ):
return QtCore.QSize( 10, 10 )
def _get_key( self, event ):
if event.isAutoRepeat():
return None
if event.key() < 256:
key = unicode(event.text())
# if the control key is being pressed, we don't get the correct
# characters, so interpret them directly from the event.key().
# Unfortunately, this means that we cannot handle key's case
# since event.key() is not case sensitive, whereas event.text() is,
# Finally, since it is not possible to get the CapsLock state
# we cannot accurately compute the case of a pressed key when
# ctrl+shift+p is pressed.
if int(event.modifiers()) & self._ctrl_modifier:
# we always get an uppercase character
key = chr(event.key())
# if shift is not being pressed, lowercase it (as mentioned,
# this does not take into account the CapsLock state)
if not int(event.modifiers()) & QtCore.Qt.ShiftModifier:
key = key.lower()
key = self.keyvald.get(event.key())
if key is not None:
# prepend the ctrl, alt, super keys if appropriate (sorted in that order)
for modifier, prefix, Qt_key in self._modifier_keys:
if event.key() != Qt_key and int(event.modifiers()) & modifier == modifier:
key = u'{}+{}'.format(prefix, key)
return key
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.
optional arguments:
Timer interval in milliseconds
Sequence of (func, args, kwargs) where func(*args, **kwargs) will
be executed by the timer every *interval*.
return TimerQT(*args, **kwargs)
def flush_events(self):
def start_event_loop(self,timeout):
def stop_event_loop(self):
def draw_idle(self):
'update drawing area only if idle'
d = self._idle
self._idle = False
def idle_draw(*args):
self._idle = True
if d: QtCore.QTimer.singleShot(0, idle_draw)
class FigureManagerQT( FigureManagerBase ):
Public attributes
canvas : The FigureCanvas instance
num : The Figure number
toolbar : The qt.QToolBar
window : The qt.QMainWindow
def __init__( self, canvas, num ):
if DEBUG: print('FigureManagerQT.%s' % fn_name())
FigureManagerBase.__init__( self, canvas, num )
self.canvas = canvas
self.window = QtGui.QMainWindow()
self.window.setWindowTitle("Figure %d" % num)
image = os.path.join( matplotlib.rcParams['datapath'],'images','matplotlib.png' )
self.window.setWindowIcon(QtGui.QIcon( image ))
# Give the keyboard focus to the figure instead of the
# manager; StrongFocus accepts both tab and click to focus and
# will enable the canvas to process event w/o clicking.
# ClickFocus only takes the focus is the window has been
# clicked
# on.
self.canvas.setFocusPolicy( QtCore.Qt.StrongFocus )
QtCore.QObject.connect( self.window, QtCore.SIGNAL( 'destroyed()' ),
self._widgetclosed )
self.window._destroying = False
self.toolbar = self._get_toolbar(self.canvas, self.window)
if self.toolbar is not None:
QtCore.QObject.connect(self.toolbar, QtCore.SIGNAL("message"),
tbs_height = self.toolbar.sizeHint().height()
tbs_height = 0
# resize the main window so it will display the canvas with the
# requested size:
cs = canvas.sizeHint()
sbs = self.window.statusBar().sizeHint()
self._status_and_tool_height = tbs_height+sbs.height()
height = cs.height() + self._status_and_tool_height
self.window.resize(cs.width(), height)
if matplotlib.is_interactive():
def notify_axes_change( fig ):
# This will be called whenever the current axes is changed
if self.toolbar is not None:
self.canvas.figure.add_axobserver( notify_axes_change )
def _show_message(self,s):
# Fixes a PySide segfault.
def full_screen_toggle(self):
if self.window.isFullScreen():
def _widgetclosed( self ):
if self.window._destroying: return
self.window._destroying = True
except AttributeError:
# It seems that when the python session is killed,
# Gcf can get destroyed before the Gcf.destroy
# line is run, leading to a useless AttributeError.
def _get_toolbar(self, canvas, parent):
# must be inited after the window, drawingArea and figure
# attrs are set
if matplotlib.rcParams['toolbar'] == 'classic':
print("Classic toolbar is not supported")
elif matplotlib.rcParams['toolbar'] == 'toolbar2':
toolbar = NavigationToolbar2QT(canvas, parent, False)
toolbar = None
return toolbar
def resize(self, width, height):
'set the canvas size in pixels'
self.window.resize(width, height + self._status_and_tool_height)
def show(self):
def destroy( self, *args ):
# check for qApp first, as PySide deletes it in its atexit handler
if QtGui.QApplication.instance() is None: return
if self.window._destroying: return
self.window._destroying = True
QtCore.QObject.disconnect( self.window, QtCore.SIGNAL( 'destroyed()' ),
self._widgetclosed )
if self.toolbar: self.toolbar.destroy()
if DEBUG: print("destroy figure manager")
def get_window_title(self):
return str(self.window.windowTitle())
def set_window_title(self, title):
class NavigationToolbar2QT( NavigationToolbar2, QtGui.QToolBar ):
def __init__(self, canvas, parent, coordinates=True):
""" coordinates: should we show the coordinates on the right? """
self.canvas = canvas
self.coordinates = coordinates
self._actions = {}
"""A mapping of toolitem method names to their QActions"""
QtGui.QToolBar.__init__( self, parent )
NavigationToolbar2.__init__( self, canvas )
def _icon(self, name):
return QtGui.QIcon(os.path.join(self.basedir, name))
def _init_toolbar(self):
self.basedir = os.path.join(matplotlib.rcParams[ 'datapath' ],'images')
for text, tooltip_text, image_file, callback in self.toolitems:
if text is None:
a = self.addAction(self._icon(image_file + '.png'),
text, getattr(self, callback))
self._actions[callback] = a
if callback in ['zoom', 'pan']:
if tooltip_text is not None:
if figureoptions is not None:
a = self.addAction(self._icon("qt4_editor_options.png"),
'Customize', self.edit_parameters)
a.setToolTip('Edit curves line and axes parameters')
self.buttons = {}
# Add the x,y location widget at the right side of the toolbar
# The stretch factor is 1 which means any resizing of the toolbar
# will resize this label instead of the buttons.
if self.coordinates:
self.locLabel = QtGui.QLabel( "", self )
QtCore.Qt.AlignRight | QtCore.Qt.AlignTop )
labelAction = self.addWidget(self.locLabel)
# reference holder for subplots_adjust window
self.adj_window = None
if figureoptions is not None:
def edit_parameters(self):
allaxes = self.canvas.figure.get_axes()
if len(allaxes) == 1:
axes = allaxes[0]
titles = []
for axes in allaxes:
title = axes.get_title()
ylabel = axes.get_ylabel()
if title:
fmt = "%(title)s"
if ylabel:
fmt += ": %(ylabel)s"
fmt += " (%(axes_repr)s)"
elif ylabel:
fmt = "%(axes_repr)s (%(ylabel)s)"
fmt = "%(axes_repr)s"
titles.append(fmt % dict(title = title,
ylabel = ylabel,
axes_repr = repr(axes)))
item, ok = QtGui.QInputDialog.getItem(self, 'Customize',
'Select axes:', titles,
0, False)
if ok:
axes = allaxes[titles.index(unicode(item))]
figureoptions.figure_edit(axes, self)
def _update_buttons_checked(self):
#sync button checkstates to match active mode
self._actions['pan'].setChecked(self._active == 'PAN')
self._actions['zoom'].setChecked(self._active == 'ZOOM')
def pan(self, *args):
super(NavigationToolbar2QT, self).pan(*args)
def zoom(self, *args):
super(NavigationToolbar2QT, self).zoom(*args)
def dynamic_update( self ):
def set_message( self, s ):
self.emit(QtCore.SIGNAL("message"), s)
if self.coordinates:
self.locLabel.setText(s.replace(', ', '\n'))
def set_cursor( self, cursor ):
if DEBUG: print('Set cursor' , cursor)
def draw_rubberband( self, event, x0, y0, x1, y1 ):
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.canvas.drawRectangle( rect )
def configure_subplots(self):
self.adj_window = QtGui.QMainWindow()
win = self.adj_window
win.setWindowTitle("Subplot Configuration Tool")
image = os.path.join( matplotlib.rcParams['datapath'],'images','matplotlib.png' )
win.setWindowIcon(QtGui.QIcon( image ))
tool = SubplotToolQt(self.canvas.figure, win)
win.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
def _get_canvas(self, fig):
return FigureCanvasQT(fig)
def save_figure(self, *args):
filetypes = self.canvas.get_supported_filetypes_grouped()
sorted_filetypes = filetypes.items()
default_filetype = self.canvas.get_default_filetype()
start = self.canvas.get_default_filename()
filters = []
selectedFilter = None
for name, exts in sorted_filetypes:
exts_list = " ".join(['*.%s' % ext for ext in exts])
filter = '%s (%s)' % (name, exts_list)
if default_filetype in exts:
selectedFilter = filter
filters = ';;'.join(filters)
fname = _getSaveFileName(self, "Choose a filename to save to",
start, filters, selectedFilter)
if fname:
self.canvas.print_figure( unicode(fname) )
except Exception as e:
self, "Error saving file", str(e),
QtGui.QMessageBox.Ok, QtGui.QMessageBox.NoButton)
class SubplotToolQt( SubplotTool, QtGui.QWidget ):
def __init__(self, targetfig, parent):
QtGui.QWidget.__init__(self, None)
self.targetfig = targetfig
self.parent = parent
self.sliderleft = QtGui.QSlider(QtCore.Qt.Horizontal)
self.sliderbottom = QtGui.QSlider(QtCore.Qt.Vertical)
self.sliderright = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slidertop = QtGui.QSlider(QtCore.Qt.Vertical)
self.sliderwspace = QtGui.QSlider(QtCore.Qt.Horizontal)
self.sliderhspace = QtGui.QSlider(QtCore.Qt.Vertical)
# constraints
QtCore.QObject.connect( self.sliderleft,
QtCore.SIGNAL( "valueChanged(int)" ),
self.sliderright.setMinimum )
QtCore.QObject.connect( self.sliderright,
QtCore.SIGNAL( "valueChanged(int)" ),
self.sliderleft.setMaximum )
QtCore.QObject.connect( self.sliderbottom,
QtCore.SIGNAL( "valueChanged(int)" ),
self.slidertop.setMinimum )
QtCore.QObject.connect( self.slidertop,
QtCore.SIGNAL( "valueChanged(int)" ),
self.sliderbottom.setMaximum )
sliders = (self.sliderleft, self.sliderbottom, self.sliderright,
self.slidertop, self.sliderwspace, self.sliderhspace, )
adjustments = ('left:', 'bottom:', 'right:', 'top:', 'wspace:', 'hspace:')
for slider, adjustment in zip(sliders, adjustments):
layout = QtGui.QGridLayout()
leftlabel = QtGui.QLabel('left')
layout.addWidget(leftlabel, 2, 0)
layout.addWidget(self.sliderleft, 2, 1)
toplabel = QtGui.QLabel('top')
layout.addWidget(toplabel, 0, 2)
layout.addWidget(self.slidertop, 1, 2)
layout.setAlignment(self.slidertop, QtCore.Qt.AlignHCenter)
bottomlabel = QtGui.QLabel('bottom')
layout.addWidget(QtGui.QLabel('bottom'), 4, 2)
layout.addWidget(self.sliderbottom, 3, 2)
layout.setAlignment(self.sliderbottom, QtCore.Qt.AlignHCenter)
rightlabel = QtGui.QLabel('right')
layout.addWidget(rightlabel, 2, 4)
layout.addWidget(self.sliderright, 2, 3)
hspacelabel = QtGui.QLabel('hspace')
layout.addWidget(hspacelabel, 0, 6)
layout.setAlignment(hspacelabel, QtCore.Qt.AlignHCenter)
layout.addWidget(self.sliderhspace, 1, 6)
layout.setAlignment(self.sliderhspace, QtCore.Qt.AlignHCenter)
wspacelabel = QtGui.QLabel('wspace')
layout.addWidget(wspacelabel, 4, 6)
layout.setAlignment(wspacelabel, QtCore.Qt.AlignHCenter)
layout.addWidget(self.sliderwspace, 3, 6)
layout.setAlignment(self.sliderwspace, QtCore.Qt.AlignBottom)
QtCore.QObject.connect( self.sliderleft,
QtCore.SIGNAL( "valueChanged(int)" ),
self.funcleft )
QtCore.QObject.connect( self.sliderbottom,
QtCore.SIGNAL( "valueChanged(int)" ),
self.funcbottom )
QtCore.QObject.connect( self.sliderright,
QtCore.SIGNAL( "valueChanged(int)" ),
self.funcright )
QtCore.QObject.connect( self.slidertop,
QtCore.SIGNAL( "valueChanged(int)" ),
self.functop )
QtCore.QObject.connect( self.sliderwspace,
QtCore.SIGNAL( "valueChanged(int)" ),
self.funcwspace )
QtCore.QObject.connect( self.sliderhspace,
QtCore.SIGNAL( "valueChanged(int)" ),
self.funchspace )
def funcleft(self, val):
if val == self.sliderright.value():
val -= 1
if self.drawon: self.targetfig.canvas.draw()
def funcright(self, val):
if val == self.sliderleft.value():
val += 1
if self.drawon: self.targetfig.canvas.draw()
def funcbottom(self, val):
if val == self.slidertop.value():
val -= 1
if self.drawon: self.targetfig.canvas.draw()
def functop(self, val):
if val == self.sliderbottom.value():
val += 1
if self.drawon: self.targetfig.canvas.draw()
def funcwspace(self, val):
if self.drawon: self.targetfig.canvas.draw()
def funchspace(self, val):
if self.drawon: self.targetfig.canvas.draw()
def error_msg_qt( msg, parent=None ):
if not is_string_like( msg ):
msg = ','.join( map( str,msg ) )
QtGui.QMessageBox.warning( None, "Matplotlib", msg, QtGui.QMessageBox.Ok )
def exception_handler( type, value, tb ):
"""Handle uncaught exceptions
It does not catch SystemExit
msg = ''
# get the filename attribute if available (for IOError)
if hasattr(value, 'filename') and value.filename != None:
msg = value.filename + ': '
if hasattr(value, 'strerror') and value.strerror != None:
msg += value.strerror
msg += str(value)
if len( msg ) : error_msg_qt( msg )
FigureManager = FigureManagerQT
Jump to Line
Something went wrong with that request. Please try again.