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

Skip to content

Commit 71ed7d6

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 0ba2b40 commit 71ed7d6

File tree

9 files changed

+130
-113
lines changed

9 files changed

+130
-113
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
@@ -2637,11 +2637,11 @@ def set_window_title(self, title):
26372637

26382638
class NavigationToolbar2:
26392639
"""
2640-
Base class for the navigation cursor, version 2
2640+
Base class for the navigation cursor, version 2.
26412641
2642-
backends must implement a canvas that handles connections for
2642+
Backends must implement a canvas that handles connections for
26432643
'button_press_event' and 'button_release_event'. See
2644-
:meth:`FigureCanvasBase.mpl_connect` for more information
2644+
:meth:`FigureCanvasBase.mpl_connect` for more information.
26452645
26462646
They must also define
26472647
@@ -2651,9 +2651,6 @@ class NavigationToolbar2:
26512651
:meth:`set_cursor`
26522652
if you want the pointer icon to change
26532653
2654-
:meth:`_init_toolbar`
2655-
create your toolbar widget
2656-
26572654
:meth:`draw_rubberband` (optional)
26582655
draw the zoom to rect "rubberband" rectangle
26592656
@@ -2672,6 +2669,12 @@ class NavigationToolbar2:
26722669
you can change the history back / forward buttons to
26732670
indicate disabled / enabled state.
26742671
2672+
and override ``__init__`` to set up the toolbar -- without forgetting to
2673+
call the base-class init. Typically, ``__init__`` needs to set up toolbar
2674+
buttons connected to the `home`, `back`, `forward`, `pan`, `zoom`, and
2675+
`save_figure` methods and using standard icons in the "images" subdirectory
2676+
of the data path.
2677+
26752678
That's it, we'll do the rest!
26762679
"""
26772680

@@ -2704,7 +2707,19 @@ def __init__(self, canvas):
27042707
self._active = None
27052708
# This cursor will be set after the initial draw.
27062709
self._lastCursor = cursors.POINTER
2707-
self._init_toolbar()
2710+
2711+
init = self._init_toolbar
2712+
if (init != NavigationToolbar2._init_toolbar.__get__(self)
2713+
and (getattr(getattr(init, "__code__", None), "co_code", None)
2714+
!= (lambda: None).__code__.co_code)):
2715+
cbook.warn_deprecated(
2716+
"3.3", name="_init_toolbar", obj_type="method",
2717+
addendum="Please fully initialize the toolbar in your "
2718+
"subclass' __init__; a fully empty _init_toolbar "
2719+
"implementation may be kept for compatibility with earlier "
2720+
"versions of Matplotlib.")
2721+
init()
2722+
27082723
self._id_drag = self.canvas.mpl_connect(
27092724
'motion_notify_event', self.mouse_move)
27102725
self._id_zoom = None
@@ -2763,6 +2778,7 @@ def home(self, *args):
27632778
self.set_history_buttons()
27642779
self._update_view()
27652780

2781+
@cbook.deprecated("3.3", alternative="__init__")
27662782
def _init_toolbar(self):
27672783
"""
27682784
This is where you actually build the GUI widgets (called by

lib/matplotlib/backends/_backend_tk.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,27 @@ def __init__(self, canvas, window, *, pack_toolbar=True):
504504
# Avoid using self.window (prefer self.canvas.get_tk_widget().master),
505505
# so that Tool implementations can reuse the methods.
506506
self.window = window
507+
508+
xmin, xmax = self.canvas.figure.bbox.intervalx
509+
height, width = 50, xmax-xmin
510+
tk.Frame.__init__(self, master=self.window,
511+
width=int(width), height=int(height),
512+
borderwidth=2)
513+
514+
for text, tooltip_text, image_file, callback in self.toolitems:
515+
if text is None:
516+
# Add a spacer; return value is unused.
517+
self._Spacer()
518+
else:
519+
button = self._Button(text=text, file=image_file,
520+
command=getattr(self, callback))
521+
if tooltip_text is not None:
522+
ToolTip.createToolTip(button, tooltip_text)
523+
524+
self.message = tk.StringVar(master=self)
525+
self._message_label = tk.Label(master=self, textvariable=self.message)
526+
self._message_label.pack(side=tk.RIGHT)
527+
507528
NavigationToolbar2.__init__(self, canvas)
508529
if pack_toolbar:
509530
self.pack(side=tk.BOTTOM, fill=tk.X)
@@ -549,29 +570,6 @@ def _Spacer(self):
549570
s.pack(side=tk.LEFT, padx=5)
550571
return s
551572

552-
def _init_toolbar(self):
553-
xmin, xmax = self.canvas.figure.bbox.intervalx
554-
height, width = 50, xmax-xmin
555-
tk.Frame.__init__(self, master=self.window,
556-
width=int(width), height=int(height),
557-
borderwidth=2)
558-
559-
self.update() # Make axes menu
560-
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-
button = self._Button(text=text, file=image_file,
567-
command=getattr(self, callback))
568-
if tooltip_text is not None:
569-
ToolTip.createToolTip(button, tooltip_text)
570-
571-
self.message = tk.StringVar(master=self)
572-
self._message_label = tk.Label(master=self, textvariable=self.message)
573-
self._message_label.pack(side=tk.RIGHT)
574-
575573
def configure_subplots(self):
576574
toolfig = Figure(figsize=(6, 3))
577575
window = tk.Toplevel()

lib/matplotlib/backends/backend_gtk3.py

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

492-
def _init_toolbar(self):
493461
self.set_style(Gtk.ToolbarStyle.ICONS)
494462

495463
self._gtk_ids = {}
@@ -523,6 +491,38 @@ def _init_toolbar(self):
523491

524492
self.show_all()
525493

494+
NavigationToolbar2.__init__(self, canvas)
495+
self.ctx = None
496+
497+
def set_message(self, s):
498+
self.message.set_label(s)
499+
500+
def set_cursor(self, cursor):
501+
self.canvas.get_property("window").set_cursor(cursord[cursor])
502+
Gtk.main_iteration()
503+
504+
def draw_rubberband(self, event, x0, y0, x1, y1):
505+
# adapted from
506+
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744
507+
self.ctx = self.canvas.get_property("window").cairo_create()
508+
509+
# todo: instead of redrawing the entire figure, copy the part of
510+
# the figure that was covered by the previous rubberband rectangle
511+
self.canvas.draw()
512+
513+
height = self.canvas.figure.bbox.height
514+
y1 = height - y1
515+
y0 = height - y0
516+
w = abs(x1 - x0)
517+
h = abs(y1 - y0)
518+
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
519+
520+
self.ctx.new_path()
521+
self.ctx.set_line_width(0.5)
522+
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
523+
self.ctx.set_source_rgb(0, 0, 0)
524+
self.ctx.stroke()
525+
526526
def _update_buttons_checked(self):
527527
for name, active in [("Pan", "PAN"), ("Zoom", "ZOOM")]:
528528
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
@@ -656,36 +656,12 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
656656

657657
def __init__(self, canvas, parent, coordinates=True):
658658
"""coordinates: should we show the coordinates on the right?"""
659+
QtWidgets.QToolBar.__init__(self, parent)
660+
659661
self._parent = parent
660662
self.coordinates = coordinates
661663
self._actions = {} # mapping of toolitem method names to QActions.
662-
QtWidgets.QToolBar.__init__(self, parent)
663-
NavigationToolbar2.__init__(self, canvas)
664-
665-
@cbook.deprecated("3.3", alternative="self.canvas.parent()")
666-
@property
667-
def parent(self):
668-
return self._parent
669-
670-
@cbook.deprecated(
671-
"3.3", alternative="os.path.join(mpl.get_data_path(), 'images')")
672-
@property
673-
def basedir(self):
674-
return str(cbook._get_data_path('images'))
675-
676-
def _icon(self, name, color=None):
677-
if is_pyqt5():
678-
name = name.replace('.png', '_large.png')
679-
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
680-
qt_compat._setDevicePixelRatio(pm, self.canvas._dpi_ratio)
681-
if color is not None:
682-
mask = pm.createMaskFromColor(QtGui.QColor('black'),
683-
QtCore.Qt.MaskOutColor)
684-
pm.fill(color)
685-
pm.setMask(mask)
686-
return QtGui.QIcon(pm)
687664

688-
def _init_toolbar(self):
689665
background_color = self.palette().color(self.backgroundRole())
690666
foreground_color = self.palette().color(self.foregroundRole())
691667
icon_color = (foreground_color
@@ -716,6 +692,31 @@ def _init_toolbar(self):
716692
labelAction = self.addWidget(self.locLabel)
717693
labelAction.setVisible(True)
718694

695+
NavigationToolbar2.__init__(self, canvas)
696+
697+
@cbook.deprecated("3.3", alternative="self.canvas.parent()")
698+
@property
699+
def parent(self):
700+
return self._parent
701+
702+
@cbook.deprecated(
703+
"3.3", alternative="os.path.join(mpl.get_data_path(), 'images')")
704+
@property
705+
def basedir(self):
706+
return str(cbook._get_data_path('images'))
707+
708+
def _icon(self, name, color=None):
709+
if is_pyqt5():
710+
name = name.replace('.png', '_large.png')
711+
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
712+
qt_compat._setDevicePixelRatio(pm, qt_compat._devicePixelRatio(self))
713+
if color is not None:
714+
mask = pm.createMaskFromColor(QtGui.QColor('black'),
715+
QtCore.Qt.MaskOutColor)
716+
pm.fill(color)
717+
pm.setMask(mask)
718+
return QtGui.QIcon(pm)
719+
719720
def edit_parameters(self):
720721
axes = self.canvas.figure.get_axes()
721722
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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,23 +1116,6 @@ def _set_frame_icon(frame):
11161116
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
11171117
def __init__(self, canvas):
11181118
wx.ToolBar.__init__(self, canvas.GetParent(), -1)
1119-
NavigationToolbar2.__init__(self, canvas)
1120-
self.canvas = canvas
1121-
self._idle = True
1122-
self.prevZoomRect = None
1123-
# for now, use alternate zoom-rectangle drawing on all
1124-
# Macs. N.B. In future versions of wx it may be possible to
1125-
# detect Retina displays with window.GetContentScaleFactor()
1126-
# and/or dc.GetContentScaleFactor()
1127-
self.retinaFix = 'wxMac' in wx.PlatformInfo
1128-
1129-
def get_canvas(self, frame, fig):
1130-
return type(self.canvas)(frame, -1, fig)
1131-
1132-
def _init_toolbar(self):
1133-
_log.debug("%s - _init_toolbar", type(self))
1134-
1135-
self._parent = self.canvas.GetParent()
11361119

11371120
self.wx_ids = {}
11381121
for text, tooltip_text, image_file, callback in self.toolitems:
@@ -1153,6 +1136,18 @@ def _init_toolbar(self):
11531136

11541137
self.Realize()
11551138

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