Skip to content

Commit 08219c2

Browse files
committed
Toolbars keep history if axes change
1 parent e5b7e45 commit 08219c2

8 files changed

+117
-77
lines changed

lib/matplotlib/backend_bases.py

+60-15
Original file line numberDiff line numberDiff line change
@@ -2734,6 +2734,7 @@ def __init__(self, canvas):
27342734
self._idRelease = None
27352735
self._active = None
27362736
self._lastCursor = None
2737+
self._home_views = {}
27372738
self._init_toolbar()
27382739
self._idDrag = self.canvas.mpl_connect(
27392740
'motion_notify_event', self.mouse_move)
@@ -2753,6 +2754,7 @@ def set_message(self, s):
27532754

27542755
def back(self, *args):
27552756
"""move back up the view lim stack"""
2757+
self._update_home_views()
27562758
self._views.back()
27572759
self._positions.back()
27582760
self.set_history_buttons()
@@ -2771,13 +2773,15 @@ def remove_rubberband(self):
27712773

27722774
def forward(self, *args):
27732775
"""Move forward in the view lim stack"""
2776+
self._update_home_views()
27742777
self._views.forward()
27752778
self._positions.forward()
27762779
self.set_history_buttons()
27772780
self._update_view()
27782781

27792782
def home(self, *args):
27802783
"""Restore the original view"""
2784+
self._update_home_views()
27812785
self._views.home()
27822786
self._positions.home()
27832787
self.set_history_buttons()
@@ -2968,19 +2972,48 @@ def _switch_off_zoom_mode(self, event):
29682972
self.mouse_move(event)
29692973

29702974
def push_current(self):
2971-
"""push the current view limits and position onto the stack"""
2972-
views = []
2973-
pos = []
2975+
"""
2976+
Push the current view limits and position onto their respective stacks
2977+
"""
2978+
2979+
views = {}
2980+
pos = {}
29742981
for a in self.canvas.figure.get_axes():
2975-
views.append(a._get_view())
2976-
# Store both the original and modified positions
2977-
pos.append((
2978-
a.get_position(True).frozen(),
2979-
a.get_position().frozen()))
2982+
views[a] = a._get_view()
2983+
pos[a] = self._axes_pos(a)
29802984
self._views.push(views)
29812985
self._positions.push(pos)
2986+
self._update_home_views()
29822987
self.set_history_buttons()
29832988

2989+
def _axes_pos(self, ax):
2990+
"""
2991+
Return the original and modified positions for the specified axes
2992+
2993+
Parameters
2994+
----------
2995+
ax : (matplotlib.axes.AxesSubplot)
2996+
The axes to get the positions for
2997+
2998+
Returns
2999+
-------
3000+
limits : (tuple)
3001+
A tuple of the original and modified positions
3002+
"""
3003+
3004+
return (ax.get_position(True).frozen(),
3005+
ax.get_position().frozen())
3006+
3007+
def _update_home_views(self):
3008+
"""
3009+
Make sure that self._home_views has an entry for all axes present in the
3010+
figure
3011+
"""
3012+
3013+
for a in self.canvas.figure.get_axes():
3014+
if a not in self._home_views:
3015+
self._home_views[a] = a._get_view()
3016+
29843017
def release(self, event):
29853018
"""this will be called whenever mouse button is released"""
29863019
pass
@@ -3106,8 +3139,11 @@ def draw(self):
31063139
self.canvas.draw_idle()
31073140

31083141
def _update_view(self):
3109-
"""Update the viewlim and position from the view and
3110-
position stack for each axes
3142+
"""
3143+
Update the view limits and position for each axes from the current stack
3144+
position. If any axes are present in the figure that aren't in the
3145+
current stack position, use the home view limits for those axes and
3146+
don't update *any* positions.
31113147
"""
31123148

31133149
views = self._views()
@@ -3116,11 +3152,19 @@ def _update_view(self):
31163152
pos = self._positions()
31173153
if pos is None:
31183154
return
3119-
for i, a in enumerate(self.canvas.figure.get_axes()):
3120-
a._set_view(views[i])
3121-
# Restore both the original and modified positions
3122-
a.set_position(pos[i][0], 'original')
3123-
a.set_position(pos[i][1], 'active')
3155+
all_axes = self.canvas.figure.get_axes()
3156+
for a in all_axes:
3157+
if a in views:
3158+
cur_view = views[a]
3159+
else:
3160+
cur_view = self._home_views[a]
3161+
a._set_view(cur_view)
3162+
3163+
if set(all_axes).issubset(pos.keys()):
3164+
for a in all_axes:
3165+
# Restore both the original and modified positions
3166+
a.set_position(pos[a][0], 'original')
3167+
a.set_position(pos[a][1], 'active')
31243168

31253169
self.canvas.draw_idle()
31263170

@@ -3137,6 +3181,7 @@ def set_cursor(self, cursor):
31373181

31383182
def update(self):
31393183
"""Reset the axes stack"""
3184+
self._home_views.clear()
31403185
self._views.clear()
31413186
self._positions.clear()
31423187
self.set_history_buttons()

lib/matplotlib/backend_tools.py

+57-24
Original file line numberDiff line numberDiff line change
@@ -445,29 +445,24 @@ class ToolViewsPositions(ToolBase):
445445
def __init__(self, *args, **kwargs):
446446
self.views = WeakKeyDictionary()
447447
self.positions = WeakKeyDictionary()
448+
self.home_views = WeakKeyDictionary()
448449
ToolBase.__init__(self, *args, **kwargs)
449450

450451
def add_figure(self):
451452
"""Add the current figure to the stack of views and positions"""
452453
if self.figure not in self.views:
453454
self.views[self.figure] = cbook.Stack()
454455
self.positions[self.figure] = cbook.Stack()
456+
self.home_views[self.figure] = WeakKeyDictionary()
455457
# Define Home
456458
self.push_current()
457-
# Adding the clear method as axobserver, removes this burden from
458-
# the backend
459-
self.figure.add_axobserver(self.clear)
460-
461-
def clear(self, figure):
462-
"""Reset the axes stack"""
463-
if figure in self.views:
464-
self.views[figure].clear()
465-
self.positions[figure].clear()
466459

467460
def update_view(self):
468461
"""
469-
Update the viewlim and position from the view and
470-
position stack for each axes
462+
Update the view limits and position for each axes from the current
463+
stack position. If any axes are present in the figure that aren't in
464+
the current stack position, use the home view limits for those axes and
465+
don't update *any* positions.
471466
"""
472467

473468
views = self.views[self.figure]()
@@ -476,27 +471,64 @@ def update_view(self):
476471
pos = self.positions[self.figure]()
477472
if pos is None:
478473
return
479-
for i, a in enumerate(self.figure.get_axes()):
480-
a._set_view(views[i])
481-
# Restore both the original and modified positions
482-
a.set_position(pos[i][0], 'original')
483-
a.set_position(pos[i][1], 'active')
474+
home_views = self.home_views[self.figure]
475+
all_axes = self.figure.get_axes()
476+
for a in all_axes:
477+
if a in views:
478+
cur_view = views[a]
479+
else:
480+
cur_view = home_views[a]
481+
a._set_view(cur_view)
482+
483+
if set(all_axes).issubset(pos.keys()):
484+
for a in all_axes:
485+
# Restore both the original and modified positions
486+
a.set_position(pos[a][0], 'original')
487+
a.set_position(pos[a][1], 'active')
484488

485489
self.figure.canvas.draw_idle()
486490

487491
def push_current(self):
488-
"""push the current view limits and position onto the stack"""
492+
"""
493+
Push the current view limits and position onto their respective stacks
494+
"""
489495

490-
views = []
491-
pos = []
496+
views = WeakKeyDictionary()
497+
pos = WeakKeyDictionary()
492498
for a in self.figure.get_axes():
493-
views.append(a._get_view())
494-
# Store both the original and modified positions
495-
pos.append((
496-
a.get_position(True).frozen(),
497-
a.get_position().frozen()))
499+
views[a] = a._get_view()
500+
pos[a] = self._axes_pos(a)
498501
self.views[self.figure].push(views)
499502
self.positions[self.figure].push(pos)
503+
self.update_home_views()
504+
505+
def _axes_pos(self, ax):
506+
"""
507+
Return the original and modified positions for the specified axes
508+
509+
Parameters
510+
----------
511+
ax : (matplotlib.axes.AxesSubplot)
512+
The axes to get the positions for
513+
514+
Returns
515+
-------
516+
limits : (tuple)
517+
A tuple of the original and modified positions
518+
"""
519+
520+
return (ax.get_position(True).frozen(),
521+
ax.get_position().frozen())
522+
523+
def update_home_views(self):
524+
"""
525+
Make sure that self.home_views has an entry for all axes present in the
526+
figure
527+
"""
528+
529+
for a in self.figure.get_axes():
530+
if a not in self.home_views[self.figure]:
531+
self.home_views[self.figure][a] = a._get_view()
500532

501533
def refresh_locators(self):
502534
"""Redraw the canvases, update the locators"""
@@ -542,6 +574,7 @@ class ViewsPositionsBase(ToolBase):
542574

543575
def trigger(self, sender, event, data=None):
544576
self.toolmanager.get_tool(_views_positions).add_figure()
577+
self.toolmanager.get_tool(_views_positions).update_home_views()
545578
getattr(self.toolmanager.get_tool(_views_positions),
546579
self._on_trigger)()
547580
self.toolmanager.get_tool(_views_positions).update_view()

lib/matplotlib/backends/backend_gtk.py

-5
Original file line numberDiff line numberDiff line change
@@ -586,11 +586,6 @@ def destroy(*args):
586586
self.window.show()
587587
self.canvas.draw_idle()
588588

589-
def notify_axes_change(fig):
590-
'this will be called whenever the current axes is changed'
591-
if self.toolbar is not None: self.toolbar.update()
592-
self.canvas.figure.add_axobserver(notify_axes_change)
593-
594589
self.canvas.grab_focus()
595590

596591
def destroy(self, *args):

lib/matplotlib/backends/backend_gtk3.py

-8
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,6 @@ def destroy(*args):
450450
self.window.show()
451451
self.canvas.draw_idle()
452452

453-
def notify_axes_change(fig):
454-
'this will be called whenever the current axes is changed'
455-
if self.toolmanager is not None:
456-
pass
457-
elif self.toolbar is not None:
458-
self.toolbar.update()
459-
self.canvas.figure.add_axobserver(notify_axes_change)
460-
461453
self.canvas.grab_focus()
462454

463455
def destroy(self, *args):

lib/matplotlib/backends/backend_macosx.py

-5
Original file line numberDiff line numberDiff line change
@@ -388,11 +388,6 @@ def __init__(self, canvas, num):
388388
if self.toolbar is not None:
389389
self.toolbar.update()
390390

391-
def notify_axes_change(fig):
392-
'this will be called whenever the current axes is changed'
393-
if self.toolbar != None: self.toolbar.update()
394-
self.canvas.figure.add_axobserver(notify_axes_change)
395-
396391
if matplotlib.is_interactive():
397392
self.show()
398393
self.canvas.draw_idle()

lib/matplotlib/backends/backend_qt5.py

-6
Original file line numberDiff line numberDiff line change
@@ -485,12 +485,6 @@ def __init__(self, canvas, num):
485485
self.window.show()
486486
self.canvas.draw_idle()
487487

488-
def notify_axes_change(fig):
489-
# This will be called whenever the current axes is changed
490-
if self.toolbar is not None:
491-
self.toolbar.update()
492-
self.canvas.figure.add_axobserver(notify_axes_change)
493-
494488
@QtCore.Slot()
495489
def _show_message(self, s):
496490
self.statusbar_label.setText(s)

lib/matplotlib/backends/backend_tkagg.py

-8
Original file line numberDiff line numberDiff line change
@@ -551,14 +551,6 @@ def __init__(self, canvas, num, window):
551551

552552
self._shown = False
553553

554-
def notify_axes_change(fig):
555-
'this will be called whenever the current axes is changed'
556-
if self.toolmanager is not None:
557-
pass
558-
elif self.toolbar is not None:
559-
self.toolbar.update()
560-
self.canvas.figure.add_axobserver(notify_axes_change)
561-
562554
def _get_toolbar(self):
563555
if matplotlib.rcParams['toolbar'] == 'toolbar2':
564556
toolbar = NavigationToolbar2TkAgg(self.canvas, self.window)

lib/matplotlib/backends/backend_wx.py

-6
Original file line numberDiff line numberDiff line change
@@ -1363,12 +1363,6 @@ def __init__(self, canvas, num, frame):
13631363
self.tb = frame.GetToolBar()
13641364
self.toolbar = self.tb # consistent with other backends
13651365

1366-
def notify_axes_change(fig):
1367-
'this will be called whenever the current axes is changed'
1368-
if self.tb is not None:
1369-
self.tb.update()
1370-
self.canvas.figure.add_axobserver(notify_axes_change)
1371-
13721366
def show(self):
13731367
self.frame.Show()
13741368

0 commit comments

Comments
 (0)