Skip to content
This repository has been archived by the owner on May 24, 2021. It is now read-only.

Commit

Permalink
Update the MPLCanvas pr for merge with master
Browse files Browse the repository at this point in the history
  • Loading branch information
sccolbert committed Dec 9, 2012
1 parent f2c3928 commit eaa6294
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 104 deletions.
134 changes: 87 additions & 47 deletions enaml/qt/qt_mpl_canvas.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,55 @@
from .qt.QtGui import QFrame, QVBoxLayout, QCursor, QApplication
#------------------------------------------------------------------------------
# Copyright (c) 2012, Enthought, Inc.
# All rights reserved.
#
# Special thanks to Steven Silvester for contributing this module!
#------------------------------------------------------------------------------
from .qt.QtCore import Qt
from .qt import qt_api
from .qt.QtGui import QFrame, QVBoxLayout
from .qt_control import QtControl

import matplotlib
# We want matplotlib to use a Qt4 backend
matplotlib.use('Qt4Agg')
if qt_api == 'pyside':
matplotlib.rcParams['backend.qt4'] = 'PySide'
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg
from matplotlib.backends.backend_qt4 import cursord


class QtMPLCanvas(QtControl):
""" A Qt implementation of an Enaml MPLCanvas.
"""
#: Internal storage for the matplotlib figure.
_figure = None

#: Internal storage for whether or not to show the toolbar.
_toolbar_visible = False

#--------------------------------------------------------------------------
# Setup Methods
#--------------------------------------------------------------------------
def create_widget(self, parent, tree):
""" Create the underlying mpl_canvas widget.
""" Create the underlying widget.
"""
return QFrame(parent)
widget = QFrame(parent)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
widget.setLayout(layout)
return widget

def create(self, tree):
""" Create and initialize the underlying widget.
"""
super(QtMPLCanvas, self).create(tree)
self.figure = tree['figure']
self._figure = tree['figure']
self._toolbar_visible = tree['toolbar_visible']

def init_layout(self):
""" A method that allows widgets to do layout initialization.
""" Initialize the layout of the underlying widget.
This method is called after all widgets in a tree have had
their 'create' method called. It is useful for doing any
initialization related to layout.
"""
super(QtMPLCanvas, self).init_layout()
figure = self.figure
widget = self.widget()
if not figure.canvas:
canvas = FigureCanvasQTAgg(figure)
else:
canvas = figure.canvas
canvas.setParent(widget)
if not hasattr(canvas, 'toolbar'):
toolbar = NavigationToolbar2QTAgg(canvas, canvas)
else:
toolbar = canvas.toolbar
# override the set_cursor method for Pyside support
# see monkey patch below
toolbar.set_cursor = lambda cursor: set_cursor(toolbar, cursor)
vbox = QVBoxLayout()
vbox.addWidget(canvas)
vbox.addWidget(toolbar)
widget.setLayout(vbox)
widget.setMinimumSize(widget.sizeHint())
self.size_hint_updated()
# allow Matplotlib canvas keyboard events
widget.setFocusPolicy(Qt.ClickFocus)
widget.setFocus()
self.refresh_mpl_widget(notify=False)

#--------------------------------------------------------------------------
# Message Handlers
Expand All @@ -72,16 +58,70 @@ def on_action_set_figure(self, content):
""" Handle the 'set_figure' action from the Enaml widget.
"""
raise NotImplementedError
self._figure = content['figure']
self.refresh_mpl_widget()

def on_action_set_toolbar_visible(self, content):
""" Handle the 'set_toolbar_visible' action from the Enaml
widget.
"""
visible = content['toolbar_visible']
self._toolbar_visible = visible
layout = self.widget().layout()
if layout.count() == 2:
item = self.widget_item()
old_hint = item.sizeHint()
toolbar = layout.itemAt(0).widget()
toolbar.setVisible(visible)
new_hint = item.sizeHint()
if old_hint != new_hint:
self.size_hint_updated()

#--------------------------------------------------------------------------
# Widget Update Methods
#--------------------------------------------------------------------------
def refresh_mpl_widget(self, notify=True):
""" Create the mpl widget and update the underlying control.
def set_cursor(toolbar, cursor):
'''Monkey patch for NavigationToolbar Pyside support
Parameters
----------
notify : bool, optional
Whether to notify the layout system if the size hint of the
widget has changed. The default is True.
"""
# Delete the old widgets in the layout, it's too much shennigans
# to try to reuse old widgets. If the size hint event should
# be emitted, compute the old size hint first.
widget = self.widget()
if notify:
item = self.widget_item()
old_hint = item.sizeHint()
layout = widget.layout()
while layout.count():
layout_item = layout.takeAt(0)
layout_item.widget().deleteLater()

# Create the new figure and toolbar widgets. It seems that key
# events will not be processed without an mpl figure manager.
# However, a figure manager will create a new toplevel window,
# which is certainly not desired in this case. This appears to
# be a limitation of matplotlib. The canvas is manually set to
# visible, or QVBoxLayout will ignore it for size hinting.
figure = self._figure
if figure is not None:
canvas = FigureCanvasQTAgg(figure)
canvas.setParent(widget)
canvas.setFocusPolicy(Qt.ClickFocus)
canvas.setVisible(True)
toolbar = NavigationToolbar2QTAgg(canvas, widget)
toolbar.setVisible(self._toolbar_visible)
layout.addWidget(toolbar)
layout.addWidget(canvas)

Original method throws segmentation fault
'''
QApplication.restoreOverrideCursor()
qcursor = QCursor()
qcursor.setShape(cursord[cursor])
QApplication.setOverrideCursor(qcursor)
if notify:
new_hint = item.sizeHint()
if old_hint != new_hint:
self.size_hint_updated()

28 changes: 17 additions & 11 deletions enaml/widgets/mpl_canvas.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
#------------------------------------------------------------------------------
# Copyright (c) 2011, Enthought, Inc.
# Copyright (c) 2012, Enthought, Inc.
# All rights reserved.
#
# Special thanks to Steven Silvester for contributing this module!
#------------------------------------------------------------------------------
from traits.api import Instance
# NOTE: There shall be no imports from matplotlib in this module. Doing so
# will create an import dependency on matplotlib for the rest of Enaml!
from traits.api import Instance, Bool

from .control import Control


class MPLCanvas(Control):
""" A control to display a Matplotlib Canvas
""" A control which can be used to embded a matplotlib figure.
"""
#: The figure
#: The matplotlib figure to display in the widget.
figure = Instance('matplotlib.figure.Figure')

#: How strongly a component hugs it's contents'.
# MPLCanvas' ignore the hug by default,
# so they expand freely in width and height.
#: Whether or not the matplotlib figure toolbar is visible.
toolbar_visible = Bool(False)

#: Matplotlib figures expand freely in height and width by default.
hug_width = 'ignore'
hug_height = 'ignore'

#--------------------------------------------------------------------------
# Initialization
#--------------------------------------------------------------------------
def snapshot(self):
""" Returns the dict of creation attributes for the control.
""" Get the snapshot dict for the MPLCanvas.
"""
snap = super(MPLCanvas, self).snapshot()
snap['figure'] = self.figure
snap['toolbar_visible'] = self.toolbar_visible
return snap

def bind(self):
""" A method called after initialization which allows the widget
to bind any event handlers necessary.
""" Bind the change handlers for the MPLCanvas.
"""
super(MPLCanvas, self).bind()
self.publish_attributes('figure')
self.publish_attributes('figure', 'toolbar_visible')

124 changes: 90 additions & 34 deletions enaml/wx/wx_mpl_canvas.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,53 @@
#------------------------------------------------------------------------------
# Copyright (c) 2012, Enthought, Inc.
# All rights reserved.
#
# Special thanks to Steven Silvester for contributing this module!
#------------------------------------------------------------------------------
import wx

from .wx_control import WxControl

import matplotlib
# We want matplotlib to use a wxPython backend
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.backends.backend_wx import NavigationToolbar2Wx


class WxMPLCanvas(WxControl):
""" A Wx implementation of an Enaml MPLCanvas.
"""
#: Internal storage for the matplotlib figure.
_figure = None

#: Internal storage for whether or not to show the toolbar.
_toolbar_visible = False

#--------------------------------------------------------------------------
# Setup Methods
#--------------------------------------------------------------------------
def create_widget(self, parent, tree):
""" Create the underlying mpl_canvas widget.
""" Create the underlying widget.
"""
widget = wx.Panel(parent, wx.NewId())
widget = wx.Panel(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
widget.SetSizer(sizer)
return widget

def create(self, tree):
""" Create and initialize the underlying widget.
"""
super(WxMPLCanvas, self).create(tree)
self.figure = tree['figure']
self._figure = tree['figure']
self._toolbar_visible = tree['toolbar_visible']

def init_layout(self):
""" A method that allows widgets to do layout initialization.
""" Initialize the layout of the underlying widget.
This method is called after all widgets in a tree have had
their 'create' method called. It is useful for doing any
initialization related to layout.
"""
figure = self.figure
panel = self.widget()
sizer = wx.BoxSizer(wx.VERTICAL)
# matplotlib commands to create a canvas
if figure.canvas is None:
mpl_control = FigureCanvas(panel, panel.GetId(), figure)
else:
mpl_control = figure.canvas
sizer.Add(mpl_control, 1, wx.LEFT | wx.TOP | wx.GROW)
if not hasattr(mpl_control, 'toolbar'):
toolbar = NavigationToolbar2Wx(mpl_control)
else:
toolbar = mpl_control.toolbar
sizer.Add(toolbar, 0, wx.EXPAND)
panel.SetSizer(sizer)
size = mpl_control.GetSize()
size.height += toolbar.GetSize().height
self.set_minimum_size((size.width, size.height))
self.size_hint_updated()
# allow Matplotlib canvas keyboard events
panel.SetFocus()

super(WxMPLCanvas, self).init_layout()
self.refresh_mpl_widget(notify=False)

#--------------------------------------------------------------------------
# Message Handlers
Expand All @@ -67,4 +56,71 @@ def on_action_set_figure(self, content):
""" Handle the 'set_figure' action from the Enaml widget.
"""
raise NotImplementedError
self._figure = content['figure']
self.refresh_mpl_widget()

def on_action_set_toolbar_visible(self, content):
""" Handle the 'set_toolbar_visible' action from the Enaml
widget.
"""
visible = content['toolbar_visible']
self._toolbar_visible = visible
widget = self.widget()
sizer = widget.GetSizer()
children = sizer.GetChildren()
if len(children) == 2:
widget.Freeze()
old_hint = widget.GetBestSize()
toolbar = children[0]
toolbar.Show(visible)
new_hint = widget.GetBestSize()
if old_hint != new_hint:
self.size_hint_updated()
sizer.Layout()
widget.Thaw()

#--------------------------------------------------------------------------
# Widget Update Methods
#--------------------------------------------------------------------------
def refresh_mpl_widget(self, notify=True):
""" Create the mpl widget and update the underlying control.
Parameters
----------
notify : bool, optional
Whether to notify the layout system if the size hint of the
widget has changed. The default is True.
"""
# Delete the old widgets in the layout, it's too much shennigans
# to try to reuse old widgets. If the size hint event should
# be emitted, compute the old size hint first.
widget = self.widget()
widget.Freeze()
if notify:
old_hint = widget.GetBestSize()
sizer = widget.GetSizer()
sizer.Clear(True)

# Create the new figure and toolbar widgets. It seems that key
# events will not be processed without an mpl figure manager.
# However, a figure manager will create a new toplevel window,
# which is certainly not desired in this case. This appears to
# be a limitation of matplotlib.
figure = self._figure
if figure is not None:
canvas = FigureCanvasWxAgg(widget, -1, figure)
toolbar = NavigationToolbar2Wx(canvas)
toolbar.Show(self._toolbar_visible)
sizer.Add(toolbar, 0, wx.EXPAND)
sizer.Add(canvas, 1, wx.EXPAND)

if notify:
new_hint = widget.GetBestSize()
if old_hint != new_hint:
self.size_hint_updated()

sizer.Layout()
widget.Thaw()

Loading

0 comments on commit eaa6294

Please sign in to comment.