Skip to content

Commit

Permalink
Toolbars keep history if axes change
Browse files Browse the repository at this point in the history
  • Loading branch information
KevKeating committed Aug 3, 2015
1 parent 42a143c commit 040e55c
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 82 deletions.
98 changes: 80 additions & 18 deletions lib/matplotlib/backend_bases.py
Expand Up @@ -2719,6 +2719,7 @@ def __init__(self, canvas):
self._idRelease = None
self._active = None
self._lastCursor = None
self._home_views = {}
self._init_toolbar()
self._idDrag = self.canvas.mpl_connect(
'motion_notify_event', self.mouse_move)
Expand All @@ -2738,6 +2739,7 @@ def set_message(self, s):

def back(self, *args):
"""move back up the view lim stack"""
self._update_home_views()
self._views.back()
self._positions.back()
self.set_history_buttons()
Expand All @@ -2752,13 +2754,15 @@ def draw_rubberband(self, event, x0, y0, x1, y1):

def forward(self, *args):
"""Move forward in the view lim stack"""
self._update_home_views()
self._views.forward()
self._positions.forward()
self.set_history_buttons()
self._update_view()

def home(self, *args):
"""Restore the original view"""
self._update_home_views()
self._views.home()
self._positions.home()
self.set_history_buttons()
Expand Down Expand Up @@ -2948,21 +2952,67 @@ def _switch_off_zoom_mode(self, event):
self.mouse_move(event)

def push_current(self):
"""push the current view limits and position onto the stack"""
lims = []
pos = []
"""
Push the current view limits and position onto their respective stacks
"""

lims = {}
pos = {}
for a in self.canvas.figure.get_axes():
xmin, xmax = a.get_xlim()
ymin, ymax = a.get_ylim()
lims.append((xmin, xmax, ymin, ymax))
# Store both the original and modified positions
pos.append((
a.get_position(True).frozen(),
a.get_position().frozen()))
lims[a] = self._axes_view(a)
pos[a] = self._axes_pos(a)
self._views.push(lims)
self._positions.push(pos)
self._update_home_views()
self.set_history_buttons()

def _axes_view(self, ax):
"""
Return the current view limits for the specified axes
Parameters
----------
ax : (matplotlib.axes.AxesSubplot)
The axes to get the view limits for
Returns
-------
limits : (tuple)
A tuple of the view limits
"""

xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
return (xmin, xmax, ymin, ymax)

def _axes_pos(self, ax):
"""
Return the original and modified positions for the specified axes
Parameters
----------
ax : (matplotlib.axes.AxesSubplot)
The axes to get the positions for
Returns
-------
limits : (tuple)
A tuple of the original and modified positions
"""

return (ax.get_position(True).frozen(),
ax.get_position().frozen())

def _update_home_views(self):
"""
Make sure that self._home_views has an entry for all axes present in the
figure
"""

for a in self.canvas.figure.get_axes():
if a not in self._home_views:
self._home_views[a] = self._axes_view(a)

def release(self, event):
"""this will be called whenever mouse button is released"""
pass
Expand Down Expand Up @@ -3160,8 +3210,11 @@ def draw(self):
self.canvas.draw_idle()

def _update_view(self):
"""Update the viewlim and position from the view and
position stack for each axes
"""
Update the view limits and position for each axes from the current stack
position. If any axes are present in the figure that aren't in the
current stack position, use the home view limits for those axes and
don't update *any* positions.
"""

lims = self._views()
Expand All @@ -3170,15 +3223,23 @@ def _update_view(self):
pos = self._positions()
if pos is None:
return
for i, a in enumerate(self.canvas.figure.get_axes()):
xmin, xmax, ymin, ymax = lims[i]
all_axes = self.canvas.figure.get_axes()
for a in all_axes:
if a in lims:
cur_lim = lims[a]
else:
cur_lim = self._home_views[a]
xmin, xmax, ymin, ymax = cur_lim
a.set_xlim((xmin, xmax))
a.set_ylim((ymin, ymax))
# Restore both the original and modified positions
a.set_position(pos[i][0], 'original')
a.set_position(pos[i][1], 'active')

self.canvas.draw_idle()
if set(all_axes).issubset(pos.keys()):
for a in all_axes:
# Restore both the original and modified positions
a.set_position(pos[a][0], 'original')
a.set_position(pos[a][1], 'active')

self.draw()

def save_figure(self, *args):
"""Save the current figure"""
Expand All @@ -3193,6 +3254,7 @@ def set_cursor(self, cursor):

def update(self):
"""Reset the axes stack"""
self._home_views.clear()
self._views.clear()
self._positions.clear()
self.set_history_buttons()
Expand Down
102 changes: 76 additions & 26 deletions lib/matplotlib/backend_tools.py
Expand Up @@ -441,29 +441,24 @@ class ToolViewsPositions(ToolBase):
def __init__(self, *args, **kwargs):
self.views = WeakKeyDictionary()
self.positions = WeakKeyDictionary()
self.home_views = WeakKeyDictionary()
ToolBase.__init__(self, *args, **kwargs)

def add_figure(self):
"""Add the current figure to the stack of views and positions"""
if self.figure not in self.views:
self.views[self.figure] = cbook.Stack()
self.positions[self.figure] = cbook.Stack()
self.home_views[self.figure] = WeakKeyDictionary()
# Define Home
self.push_current()
# Adding the clear method as axobserver, removes this burden from
# the backend
self.figure.add_axobserver(self.clear)

def clear(self, figure):
"""Reset the axes stack"""
if figure in self.views:
self.views[figure].clear()
self.positions[figure].clear()

def update_view(self):
"""
Update the viewlim and position from the view and
position stack for each axes
Update the view limits and position for each axes from the current stack
position. If any axes are present in the figure that aren't in the
current stack position, use the home view limits for those axes and
don't update *any* positions.
"""

lims = self.views[self.figure]()
Expand All @@ -472,31 +467,85 @@ def update_view(self):
pos = self.positions[self.figure]()
if pos is None:
return
for i, a in enumerate(self.figure.get_axes()):
xmin, xmax, ymin, ymax = lims[i]
home_views = self.home_views[self.figure]
all_axes = self.figure.get_axes()
for a in all_axes:
if a in lims:
cur_lim = lims[a]
else:
cur_lim = home_views[a]
xmin, xmax, ymin, ymax = cur_lim
a.set_xlim((xmin, xmax))
a.set_ylim((ymin, ymax))
# Restore both the original and modified positions
a.set_position(pos[i][0], 'original')
a.set_position(pos[i][1], 'active')

if set(all_axes).issubset(pos.keys()):
for a in all_axes:
# Restore both the original and modified positions
a.set_position(pos[a][0], 'original')
a.set_position(pos[a][1], 'active')

self.figure.canvas.draw_idle()

def push_current(self):
"""push the current view limits and position onto the stack"""
"""
Push the current view limits and position onto their respective stacks
"""

lims = []
pos = []
lims = WeakKeyDictionary()
pos = WeakKeyDictionary()
for a in self.figure.get_axes():
xmin, xmax = a.get_xlim()
ymin, ymax = a.get_ylim()
lims.append((xmin, xmax, ymin, ymax))
# Store both the original and modified positions
pos.append((
a.get_position(True).frozen(),
a.get_position().frozen()))
lims[a] = self._axes_view(a)
pos[a] = self._axes_pos(a)
self.views[self.figure].push(lims)
self.positions[self.figure].push(pos)
self.update_home_views()

def _axes_view(self, ax):
"""
Return the current view limits for the specified axes
Parameters
----------
ax : (matplotlib.axes.AxesSubplot)
The axes to get the view limits for
Returns
-------
limits : (tuple)
A tuple of the view limits
"""

xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
return (xmin, xmax, ymin, ymax)

def _axes_pos(self, ax):
"""
Return the original and modified positions for the specified axes
Parameters
----------
ax : (matplotlib.axes.AxesSubplot)
The axes to get the positions for
Returns
-------
limits : (tuple)
A tuple of the original and modified positions
"""

return (ax.get_position(True).frozen(),
ax.get_position().frozen())

def update_home_views(self):
"""
Make sure that self.home_views has an entry for all axes present in the
figure
"""

for a in self.figure.get_axes():
if a not in self.home_views[self.figure]:
self.home_views[self.figure][a] = self._axes_view(a)

def refresh_locators(self):
"""Redraw the canvases, update the locators"""
Expand Down Expand Up @@ -542,6 +591,7 @@ class ViewsPositionsBase(ToolBase):

def trigger(self, sender, event, data=None):
self.toolmanager.get_tool(_views_positions).add_figure()
self.toolmanager.get_tool(_views_positions).update_home_views()
getattr(self.toolmanager.get_tool(_views_positions),
self._on_trigger)()
self.toolmanager.get_tool(_views_positions).update_view()
Expand Down
5 changes: 0 additions & 5 deletions lib/matplotlib/backends/backend_gtk.py
Expand Up @@ -586,11 +586,6 @@ def destroy(*args):
self.window.show()
self.canvas.draw_idle()

def notify_axes_change(fig):
'this will be called whenever the current axes is changed'
if self.toolbar is not None: self.toolbar.update()
self.canvas.figure.add_axobserver(notify_axes_change)

self.canvas.grab_focus()

def destroy(self, *args):
Expand Down
8 changes: 0 additions & 8 deletions lib/matplotlib/backends/backend_gtk3.py
Expand Up @@ -450,14 +450,6 @@ def destroy(*args):
self.window.show()
self.canvas.draw_idle()

def notify_axes_change(fig):
'this will be called whenever the current axes is changed'
if self.toolmanager is not None:
pass
elif self.toolbar is not None:
self.toolbar.update()
self.canvas.figure.add_axobserver(notify_axes_change)

self.canvas.grab_focus()

def destroy(self, *args):
Expand Down
5 changes: 0 additions & 5 deletions lib/matplotlib/backends/backend_macosx.py
Expand Up @@ -387,11 +387,6 @@ def __init__(self, canvas, num):
if self.toolbar is not None:
self.toolbar.update()

def notify_axes_change(fig):
'this will be called whenever the current axes is changed'
if self.toolbar != None: self.toolbar.update()
self.canvas.figure.add_axobserver(notify_axes_change)

if matplotlib.is_interactive():
self.show()
self.canvas.draw_idle()
Expand Down
6 changes: 0 additions & 6 deletions lib/matplotlib/backends/backend_qt5.py
Expand Up @@ -485,12 +485,6 @@ def __init__(self, canvas, num):
self.window.show()
self.canvas.draw_idle()

def notify_axes_change(fig):
# This will be called whenever the current axes is changed
if self.toolbar is not None:
self.toolbar.update()
self.canvas.figure.add_axobserver(notify_axes_change)

@QtCore.Slot()
def _show_message(self, s):
# Fixes a PySide segfault.
Expand Down
8 changes: 0 additions & 8 deletions lib/matplotlib/backends/backend_tkagg.py
Expand Up @@ -551,14 +551,6 @@ def __init__(self, canvas, num, window):

self._shown = False

def notify_axes_change(fig):
'this will be called whenever the current axes is changed'
if self.toolmanager is not None:
pass
elif self.toolbar is not None:
self.toolbar.update()
self.canvas.figure.add_axobserver(notify_axes_change)

def _get_toolbar(self):
if matplotlib.rcParams['toolbar'] == 'toolbar2':
toolbar = NavigationToolbar2TkAgg(self.canvas, self.window)
Expand Down
6 changes: 0 additions & 6 deletions lib/matplotlib/backends/backend_wx.py
Expand Up @@ -1363,12 +1363,6 @@ def __init__(self, canvas, num, frame):
self.tb = frame.GetToolBar()
self.toolbar = self.tb # consistent with other backends

def notify_axes_change(fig):
'this will be called whenever the current axes is changed'
if self.tb is not None:
self.tb.update()
self.canvas.figure.add_axobserver(notify_axes_change)

def show(self):
self.frame.Show()

Expand Down

0 comments on commit 040e55c

Please sign in to comment.