Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bcbc49e

Browse files
committed
Deprecate NavigationToolbar2._init_toolbar.
As argued elsewhere, a customization point which requires third-party libraries to override a private method is awkward from the PoV of documentation and of required API stability. In fact _init_toolbar is not needed as a customization point; third-party libraries can simply override `__init__` and call `super().__init__` as appropriate. Moreover, *requiring* that `_init_toolbar` be overridden is actually overkill, e.g. for `test_backend_bases.py::test_interactive_zoom`: there, the base class NavigationToolbar2 is perfectly suitable -- see change there. In order to let third-parties write code that supports both pre- and post-deprecation versions of mpl, allow them to keep a fully empty `_init_toolbar` (an override is required by earlier versions of mpl) without triggering a deprecation warning.
1 parent 3232f58 commit bcbc49e

File tree

9 files changed

+132
-134
lines changed

9 files changed

+132
-134
lines changed

doc/api/api_changes_3.3/deprecations.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,16 @@ The ``Fil``, ``Fill``, ``Filll``, ``NegFil``, ``NegFill``, ``NegFilll``, and
366366
``SsGlue`` classes in the :mod:`matplotlib.mathtext` module are deprecated.
367367
As an alternative, directly construct glue instances with ``Glue("fil")``, etc.
368368

369+
NavigationToolbar2._init_toolbar
370+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
371+
Overriding this method to initialize third-party toolbars is deprecated.
372+
Instead, the toolbar should be initialized in the ``__init__`` method of the
373+
subclass (which should call the base-class' ``__init__`` as appropriate). To
374+
keep back-compatibility with earlier versions of Matplotlib (which *required*
375+
``_init_toolbar`` to be overridden), a fully empty implementation (``def
376+
_init_toolbar(self): pass``) may be kept and will not trigger the deprecation
377+
warning.
378+
369379
NavigationToolbar2QT.parent and .basedir
370380
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
371381
These attributes are deprecated. In order to access the parent window, use

lib/matplotlib/backend_bases.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,11 +2661,11 @@ def __str__(self):
26612661

26622662
class NavigationToolbar2:
26632663
"""
2664-
Base class for the navigation cursor, version 2
2664+
Base class for the navigation cursor, version 2.
26652665
2666-
backends must implement a canvas that handles connections for
2666+
Backends must implement a canvas that handles connections for
26672667
'button_press_event' and 'button_release_event'. See
2668-
:meth:`FigureCanvasBase.mpl_connect` for more information
2668+
:meth:`FigureCanvasBase.mpl_connect` for more information.
26692669
26702670
They must also define
26712671
@@ -2675,9 +2675,6 @@ class NavigationToolbar2:
26752675
:meth:`set_cursor`
26762676
if you want the pointer icon to change
26772677
2678-
:meth:`_init_toolbar`
2679-
create your toolbar widget
2680-
26812678
:meth:`draw_rubberband` (optional)
26822679
draw the zoom to rect "rubberband" rectangle
26832680
@@ -2696,6 +2693,12 @@ class NavigationToolbar2:
26962693
you can change the history back / forward buttons to
26972694
indicate disabled / enabled state.
26982695
2696+
and override ``__init__`` to set up the toolbar -- without forgetting to
2697+
call the base-class init. Typically, ``__init__`` needs to set up toolbar
2698+
buttons connected to the `home`, `back`, `forward`, `pan`, `zoom`, and
2699+
`save_figure` methods and using standard icons in the "images" subdirectory
2700+
of the data path.
2701+
26992702
That's it, we'll do the rest!
27002703
"""
27012704

@@ -2725,7 +2728,19 @@ def __init__(self, canvas):
27252728
self._xypress = None # location and axis info at the time of the press
27262729
# This cursor will be set after the initial draw.
27272730
self._lastCursor = cursors.POINTER
2728-
self._init_toolbar()
2731+
2732+
init = self._init_toolbar
2733+
if (init != NavigationToolbar2._init_toolbar.__get__(self)
2734+
and (getattr(getattr(init, "__code__", None), "co_code", None)
2735+
!= (lambda: None).__code__.co_code)):
2736+
cbook.warn_deprecated(
2737+
"3.3", name="_init_toolbar", obj_type="method",
2738+
addendum="Please fully initialize the toolbar in your "
2739+
"subclass' __init__; a fully empty _init_toolbar "
2740+
"implementation may be kept for compatibility with earlier "
2741+
"versions of Matplotlib.")
2742+
init()
2743+
27292744
self._id_press = self.canvas.mpl_connect(
27302745
'button_press_event', self._zoom_pan_handler)
27312746
self._id_release = self.canvas.mpl_connect(
@@ -2788,6 +2803,7 @@ def home(self, *args):
27882803
self.set_history_buttons()
27892804
self._update_view()
27902805

2806+
@cbook.deprecated("3.3", alternative="__init__")
27912807
def _init_toolbar(self):
27922808
"""
27932809
This is where you actually build the GUI widgets (called by

lib/matplotlib/backends/_backend_tk.py

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,29 @@ def __init__(self, canvas, window, *, pack_toolbar=True):
492492
# Avoid using self.window (prefer self.canvas.get_tk_widget().master),
493493
# so that Tool implementations can reuse the methods.
494494
self.window = window
495+
496+
tk.Frame.__init__(self, master=window, borderwidth=2,
497+
width=int(canvas.figure.bbox.width), height=50)
498+
499+
self._buttons = {}
500+
for text, tooltip_text, image_file, callback in self.toolitems:
501+
if text is None:
502+
# Add a spacer; return value is unused.
503+
self._Spacer()
504+
else:
505+
self._buttons[text] = button = self._Button(
506+
text,
507+
str(cbook._get_data_path(f"images/{image_file}.gif")),
508+
toggle=callback in ["zoom", "pan"],
509+
command=getattr(self, callback),
510+
)
511+
if tooltip_text is not None:
512+
ToolTip.createToolTip(button, tooltip_text)
513+
514+
self.message = tk.StringVar(master=self)
515+
self._message_label = tk.Label(master=self, textvariable=self.message)
516+
self._message_label.pack(side=tk.RIGHT)
517+
495518
NavigationToolbar2.__init__(self, canvas)
496519
if pack_toolbar:
497520
self.pack(side=tk.BOTTOM, fill=tk.X)
@@ -548,51 +571,6 @@ def _Spacer(self):
548571
s.pack(side=tk.LEFT, padx=5)
549572
return s
550573

551-
def _init_toolbar(self):
552-
xmin, xmax = self.canvas.figure.bbox.intervalx
553-
height, width = 50, xmax-xmin
554-
tk.Frame.__init__(self, master=self.window,
555-
width=int(width), height=int(height),
556-
borderwidth=2)
557-
558-
self.update() # Make axes menu
559-
560-
self._buttons = {}
561-
for text, tooltip_text, image_file, callback in self.toolitems:
562-
if text is None:
563-
# Add a spacer; return value is unused.
564-
self._Spacer()
565-
else:
566-
self._buttons[text] = button = self._Button(
567-
text,
568-
str(cbook._get_data_path(f"images/{image_file}.gif")),
569-
toggle=callback in ["zoom", "pan"],
570-
command=getattr(self, callback),
571-
)
572-
if tooltip_text is not None:
573-
ToolTip.createToolTip(button, tooltip_text)
574-
575-
self.message = tk.StringVar(master=self)
576-
self._message_label = tk.Label(master=self, textvariable=self.message)
577-
self._message_label.pack(side=tk.RIGHT)
578-
579-
def _update_buttons_checked(self):
580-
for name, mode in [("Pan", "PAN"), ("Zoom", "ZOOM")]:
581-
button = self._buttons.get(name)
582-
if button:
583-
if self.mode.name == mode and not button.var.get():
584-
button.select()
585-
elif self.mode.name != mode and button.var.get():
586-
button.deselect()
587-
588-
def pan(self, *args):
589-
super().pan(*args)
590-
self._update_buttons_checked()
591-
592-
def zoom(self, *args):
593-
super().zoom(*args)
594-
self._update_buttons_checked()
595-
596574
def configure_subplots(self):
597575
toolfig = Figure(figsize=(6, 3))
598576
window = tk.Toplevel()

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -451,39 +451,7 @@ class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar):
451451
def __init__(self, canvas, window):
452452
self.win = window
453453
GObject.GObject.__init__(self)
454-
NavigationToolbar2.__init__(self, canvas)
455-
self.ctx = None
456-
457-
def set_message(self, s):
458-
self.message.set_label(s)
459-
460-
def set_cursor(self, cursor):
461-
self.canvas.get_property("window").set_cursor(cursord[cursor])
462-
Gtk.main_iteration()
463-
464-
def draw_rubberband(self, event, x0, y0, x1, y1):
465-
# adapted from
466-
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744
467-
self.ctx = self.canvas.get_property("window").cairo_create()
468-
469-
# todo: instead of redrawing the entire figure, copy the part of
470-
# the figure that was covered by the previous rubberband rectangle
471-
self.canvas.draw()
472-
473-
height = self.canvas.figure.bbox.height
474-
y1 = height - y1
475-
y0 = height - y0
476-
w = abs(x1 - x0)
477-
h = abs(y1 - y0)
478-
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
479-
480-
self.ctx.new_path()
481-
self.ctx.set_line_width(0.5)
482-
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
483-
self.ctx.set_source_rgb(0, 0, 0)
484-
self.ctx.stroke()
485454

486-
def _init_toolbar(self):
487455
self.set_style(Gtk.ToolbarStyle.ICONS)
488456

489457
self._gtk_ids = {}
@@ -517,6 +485,38 @@ def _init_toolbar(self):
517485

518486
self.show_all()
519487

488+
NavigationToolbar2.__init__(self, canvas)
489+
self.ctx = None
490+
491+
def set_message(self, s):
492+
self.message.set_label(s)
493+
494+
def set_cursor(self, cursor):
495+
self.canvas.get_property("window").set_cursor(cursord[cursor])
496+
Gtk.main_iteration()
497+
498+
def draw_rubberband(self, event, x0, y0, x1, y1):
499+
# adapted from
500+
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744
501+
self.ctx = self.canvas.get_property("window").cairo_create()
502+
503+
# todo: instead of redrawing the entire figure, copy the part of
504+
# the figure that was covered by the previous rubberband rectangle
505+
self.canvas.draw()
506+
507+
height = self.canvas.figure.bbox.height
508+
y1 = height - y1
509+
y0 = height - y0
510+
w = abs(x1 - x0)
511+
h = abs(y1 - y0)
512+
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
513+
514+
self.ctx.new_path()
515+
self.ctx.set_line_width(0.5)
516+
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
517+
self.ctx.set_source_rgb(0, 0, 0)
518+
self.ctx.stroke()
519+
520520
def _update_buttons_checked(self):
521521
for name, active in [("Pan", "PAN"), ("Zoom", "ZOOM")]:
522522
button = self._gtk_ids.get(name)

lib/matplotlib/backends/backend_macosx.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,10 @@ def close(self):
110110
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
111111

112112
def __init__(self, canvas):
113-
NavigationToolbar2.__init__(self, canvas)
114-
115-
def _init_toolbar(self):
113+
self.canvas = canvas # Needed by the _macosx __init__.
116114
_macosx.NavigationToolbar2.__init__(
117115
self, str(cbook._get_data_path('images')))
116+
NavigationToolbar2.__init__(self, canvas)
118117

119118
def draw_rubberband(self, event, x0, y0, x1, y1):
120119
self.canvas.set_rubberband(int(x0), int(y0), int(x1), int(y1))

lib/matplotlib/backends/backend_qt5.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -639,36 +639,12 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
639639

640640
def __init__(self, canvas, parent, coordinates=True):
641641
"""coordinates: should we show the coordinates on the right?"""
642+
QtWidgets.QToolBar.__init__(self, parent)
643+
642644
self._parent = parent
643645
self.coordinates = coordinates
644646
self._actions = {} # mapping of toolitem method names to QActions.
645-
QtWidgets.QToolBar.__init__(self, parent)
646-
NavigationToolbar2.__init__(self, canvas)
647-
648-
@cbook.deprecated("3.3", alternative="self.canvas.parent()")
649-
@property
650-
def parent(self):
651-
return self._parent
652-
653-
@cbook.deprecated(
654-
"3.3", alternative="os.path.join(mpl.get_data_path(), 'images')")
655-
@property
656-
def basedir(self):
657-
return str(cbook._get_data_path('images'))
658-
659-
def _icon(self, name, color=None):
660-
if is_pyqt5():
661-
name = name.replace('.png', '_large.png')
662-
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
663-
qt_compat._setDevicePixelRatio(pm, self.canvas._dpi_ratio)
664-
if color is not None:
665-
mask = pm.createMaskFromColor(QtGui.QColor('black'),
666-
QtCore.Qt.MaskOutColor)
667-
pm.fill(color)
668-
pm.setMask(mask)
669-
return QtGui.QIcon(pm)
670647

671-
def _init_toolbar(self):
672648
background_color = self.palette().color(self.backgroundRole())
673649
foreground_color = self.palette().color(self.foregroundRole())
674650
icon_color = (foreground_color
@@ -699,6 +675,31 @@ def _init_toolbar(self):
699675
labelAction = self.addWidget(self.locLabel)
700676
labelAction.setVisible(True)
701677

678+
NavigationToolbar2.__init__(self, canvas)
679+
680+
@cbook.deprecated("3.3", alternative="self.canvas.parent()")
681+
@property
682+
def parent(self):
683+
return self._parent
684+
685+
@cbook.deprecated(
686+
"3.3", alternative="os.path.join(mpl.get_data_path(), 'images')")
687+
@property
688+
def basedir(self):
689+
return str(cbook._get_data_path('images'))
690+
691+
def _icon(self, name, color=None):
692+
if is_pyqt5():
693+
name = name.replace('.png', '_large.png')
694+
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
695+
qt_compat._setDevicePixelRatio(pm, qt_compat._devicePixelRatio(self))
696+
if color is not None:
697+
mask = pm.createMaskFromColor(QtGui.QColor('black'),
698+
QtCore.Qt.MaskOutColor)
699+
pm.fill(color)
700+
pm.setMask(mask)
701+
return QtGui.QIcon(pm)
702+
702703
def edit_parameters(self):
703704
axes = self.canvas.figure.get_axes()
704705
if not axes:

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,10 @@ class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2):
366366
(('Download', 'Download plot', 'download', 'download'),))
367367
if image_file in _JQUERY_ICON_CLASSES]
368368

369-
def _init_toolbar(self):
369+
def __init__(self, canvas):
370370
self.message = ''
371371
self.cursor = 0
372+
super().__init__(canvas)
372373

373374
def set_message(self, message):
374375
if message != self.message:

lib/matplotlib/backends/backend_wx.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,22 +1114,6 @@ def _set_frame_icon(frame):
11141114
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
11151115
def __init__(self, canvas):
11161116
wx.ToolBar.__init__(self, canvas.GetParent(), -1)
1117-
NavigationToolbar2.__init__(self, canvas)
1118-
self._idle = True
1119-
self.prevZoomRect = None
1120-
# for now, use alternate zoom-rectangle drawing on all
1121-
# Macs. N.B. In future versions of wx it may be possible to
1122-
# detect Retina displays with window.GetContentScaleFactor()
1123-
# and/or dc.GetContentScaleFactor()
1124-
self.retinaFix = 'wxMac' in wx.PlatformInfo
1125-
1126-
def get_canvas(self, frame, fig):
1127-
return type(self.canvas)(frame, -1, fig)
1128-
1129-
def _init_toolbar(self):
1130-
_log.debug("%s - _init_toolbar", type(self))
1131-
1132-
self._parent = self.canvas.GetParent()
11331117

11341118
self.wx_ids = {}
11351119
for text, tooltip_text, image_file, callback in self.toolitems:
@@ -1150,6 +1134,18 @@ def _init_toolbar(self):
11501134

11511135
self.Realize()
11521136

1137+
NavigationToolbar2.__init__(self, canvas)
1138+
self._idle = True
1139+
self.prevZoomRect = None
1140+
# for now, use alternate zoom-rectangle drawing on all
1141+
# Macs. N.B. In future versions of wx it may be possible to
1142+
# detect Retina displays with window.GetContentScaleFactor()
1143+
# and/or dc.GetContentScaleFactor()
1144+
self.retinaFix = 'wxMac' in wx.PlatformInfo
1145+
1146+
def get_canvas(self, frame, fig):
1147+
return type(self.canvas)(frame, -1, fig)
1148+
11531149
def zoom(self, *args):
11541150
self.ToggleTool(self.wx_ids['Pan'], False)
11551151
NavigationToolbar2.zoom(self, *args)

lib/matplotlib/tests/test_backend_bases.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,7 @@ def test_interactive_zoom():
106106
fig, ax = plt.subplots()
107107
ax.set(xscale="logit")
108108

109-
class NT2(NavigationToolbar2):
110-
def _init_toolbar(self): pass
111-
112-
tb = NT2(fig.canvas)
109+
tb = NavigationToolbar2(fig.canvas)
113110
tb.zoom()
114111

115112
xlim0 = ax.get_xlim()

0 commit comments

Comments
 (0)