在matplotlib工具栏源码探析二(添加、删除内置工具项)和matplotlib工具栏源码探析三(添加、删除自定义工具项)两篇文章中,仔细观察会发现,不论内置工具项还是自定义工具项都没有图标,但是默认工具项都有图标!那工具项图标设置的流程是什么呢?
根据上面列出的两篇文章可知,以pyqt5为后端,与工具栏实现相关的源码为matplotlib.backends.backend_qt5
模块的两个类NavigationToolbar2QT
和ToolbarQt
。根据NavigationToolbar2QT
和ToolbarQt
的源码可知,不管默认的toolbar2
模式或者toolmanager
模式,工具项的图标都依靠NavigationToolbar2QT
类的_icon
方法实现。
_icon
方法的唯一参数为name
,即工具项的名称.png,然后通过cbook._get_data_path('images', name)
获取图标文件的实际路径,cbook._get_data_path
方法即获取matplotlib
的数据目录,Windows下一般为matplotlib
安装目录的mpl-data
子目录。
打开mpl-data
目录的images
目录可知,目录中只有默认工具栏中的工具项对应的图标文件,比如前面用到的fullscreen
是没有对应图标文件,所以虽然fullscreen
等工具项也是内置工具项,但是添加后确是没有图标的。那是不是在在mpl-data\images
目录放上工具项对应名称的图标文件就能显示图标了呢?
mpl-data\images
目录放置图标文件添加工具项图标首先将找好的内置quit
工具项图标文件quit.png
放置到mpl-data\images
目录中。然后编写如下代码添加工具项。
add_tool
方法添加工具项import matplotlib.pyplot as plt
import matplotlib as mpl
plt.rcParams['toolbar'] = 'toolmanager'
fig = plt.gcf()
fig.canvas.manager.toolbar.add_tool('quit', 'foo')
plt.show()
add_toolitem
方法添加工具项import matplotlib.pyplot as plt
import matplotlib as mpl
plt.rcParams['toolbar'] = 'toolmanager'
fig = plt.gcf()
fig.canvas.manager.toolbar.add_toolitem('quit', 'foo', -1,'quit','quit',False)
plt.show()
add_tool
方法添加图标失败呢?查看add_tool
方法源码(matplotlib.backend_bases.ToolContainerBase
类)可知:
设置图标的关键在于image = self._get_image_filename(tool.image)
。
查看matplotlib.backend_tools
模块工具项相关类定义可知,默认工具栏的工具项对应的类中均定义了image
属性,其他内置工具项类未定义image
属性,那么就没办法显示工具栏按钮图标,即便放置了对应的图标文件!
add_toolitem
方法添加成功呢?通过源码可知,add_tool
方法依赖于add_toolitem
添加工具项,直接调用add_toolitem
方法就可以直接指定image
属性,因此设置成功。
通过上面分析可知,除默认工具栏的工具项之外的其他内置工具项之所以使用add_tool
方法设置图标失败是因为类定义中没有image
属性,那么为工具项类添加image
属性是否行得通呢?
首先先把quit.png
放置在mpl-data\images
目录中。
代码:
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolQuit
plt.rcParams['toolbar'] = 'toolmanager'
#为ToolQuit类增加image属性
ToolQuit.image = 'quit'
fig = plt.gcf()
fig.canvas.manager.toolbar.add_tool('quit', 'foo', -1)
plt.show()
运行后图标未正常显示,经过调试,将quit.png
文件名改为quit_large.png
,成功!
为什么add_toolitem
方法使用quit.png
设置成功,而add_tool
方法需要使用quit_large.png
呢?
经过追踪,发现add_toolitem
方法案例中,设置图标文件为quit
,经过一系列转换,图标文件的实际路径转换为matplotlib\mpl-data\images\quit
,在应用_icon
方法设置图标时,因为 name = name.replace('.png', '_large.png')
未成功修改文件名,因此未设置quit_large.png
为图标文件!关键是matplotlib\mpl-data\images\quit
没有扩展名是如何被设置为图标的!因为,QtGui模块是pyd文件,看不到源码,暂时好搞不明白原理。
每次把图标文件放置在mpl-data\images
目录中太繁琐,是否能够自定义图标文件路径呢?
通过add_toolitem
方法和add_toolitem
方法设置自定义工具项图标,针对图标文件路径、图标文件名称做了一些实验。
add_tool
方法import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
class NewTool(ToolBase):
image = r"C:\quit.png"
fig = plt.figure()
tm = fig.canvas.manager.toolmanager
tm.add_tool("newtool", NewTool)
fig.canvas.manager.toolbar.add_tool("newtool", "toolgroup")
plt.show()
在c盘根目录放置quit.png
和quit_large.png
两个图标文件。
c:\quit.png
,保留c:\quit_large.png
:图标未能正常显示,add_tool
方法检测图标文件是否存在,文件不存在无后续操作。c:\quit_large.png
,保留c:\quit.png
:图标未能正常显示,_icon
方法将c:\quit.png
转换为c:\quit_large.png
,找不到该文件,因此图标未能正常显示。quit.png
和quit_large.png
:图标正常显示。add_toolitem
方法import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
class NewTool(ToolBase):
pass
image = r"c:\quit.png"
fig = plt.figure()
tm = fig.canvas.manager.toolmanager
tm.add_tool("newtool", NewTool)
fig.canvas.manager.toolbar.add_toolitem("newtool", "toolgroup",-1 , image,'test',False)
plt.show()
或
import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
class NewTool(ToolBase):
image = r"c:\quit.png"
fig = plt.figure()
tm = fig.canvas.manager.toolmanager
tm.add_tool("newtool", NewTool)
fig.canvas.manager.toolbar.add_toolitem("newtool", "toolgroup",-1 , NewTool.image,'test',False)
plt.show()
在c盘根目录放置quit.png
和quit_large.png
两个图标文件。
c:\quit.png
,保留c:\quit_large.png
:图标正常显示,说明add_toolitem
方法并不被检测文件是否存在,接着_icon
方法将c:\quit.png
转换为c:\quit_large.png
,因此图标正常显示。c:\quit_large.png
,保留c:\quit.png
:图标未能正常显示,_icon
方法将c:\quit.png
转换为c:\quit_large.png
,找不到该文件,因此图标未能正常显示。add_toolitem
方法,去掉图标文件扩展名import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
class NewTool(ToolBase):
pass
image = r"c:\quit"
fig = plt.figure()
tm = fig.canvas.manager.toolmanager
tm.add_tool("newtool", NewTool)
fig.canvas.manager.toolbar.add_toolitem("newtool", "toolgroup",-1 , image,'test',False)
plt.show()
add_toolitem
方法,只保留图标文件名称import matplotlib
matplotlib.rcParams["toolbar"] = "toolmanager"
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
class NewTool(ToolBase):
pass
image = "quit"
fig = plt.figure()
tm = fig.canvas.manager.toolmanager
tm.add_tool("newtool", NewTool)
fig.canvas.manager.toolbar.add_toolitem("newtool", "toolgroup",-1 , image,'test',False)
plt.show()
在mpl-data\images
目录中录放置quit.png
和quit_large.png
两个图标文件。
quit.png
,保留quit_large.png
:图标未能正常显示。quit_large.png
,保留quit.png
:图标正常显示。quit.png
和quit_large.png
:图标正常显示。image
属性,由于pyqt5使用带_large.png
后缀的文件作为图标,因此,最好同时放置两套图标文件。add_toolitem
方法和add_toolitem
方法都可以设置图标,但是add_toolitem
方法会检测文件是否存在,add_toolitem
方法不检测文件是否存在,add_tool
方法要求两套图标文件同时存在,add_toolitem
方法如果设置的图标文件有扩展名只需_large.png
文件存在即可。.png
作为图标文件,忽略_large.png
文件。matplotlib
相关源码NavigationToolbar2QT
和ToolbarQt
类源码class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
# 省略部分代码...
def __init__(self, canvas, parent, coordinates=True):
# 省略部分代码...
for text, tooltip_text, image_file, callback in self.toolitems:
if text is None:
self.addSeparator()
else:
a = self.addAction(self._icon(image_file + '.png'),
text, getattr(self, callback))
self._actions[callback] = a
if callback in ['zoom', 'pan']:
a.setCheckable(True)
if tooltip_text is not None:
a.setToolTip(tooltip_text)
def _icon(self, name):
"""
Construct a `.QIcon` from an image file *name*, including the extension
and relative to Matplotlib's "images" data directory.
"""
if QtCore.qVersion() >= '5.':
name = name.replace('.png', '_large.png')
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
_setDevicePixelRatioF(pm, _devicePixelRatioF(self))
if self.palette().color(self.backgroundRole()).value() < 128:
icon_color = self.palette().color(self.foregroundRole())
mask = pm.createMaskFromColor(QtGui.QColor('black'),
QtCore.Qt.MaskOutColor)
pm.fill(icon_color)
pm.setMask(mask)
return QtGui.QIcon(pm)
class ToolbarQt(ToolContainerBase, QtWidgets.QToolBar):
# 省略部分代码...
def add_toolitem(self, name, group, position, image_file, description, toggle):
button = QtWidgets.QToolButton(self)
if image_file:
button.setIcon(NavigationToolbar2QT._icon(self, image_file))
button.setText(name)
if description:
button.setToolTip(description)
def handler():
self.trigger_tool(name)
if toggle:
button.setCheckable(True)
button.toggled.connect(handler)
else:
button.clicked.connect(handler)
self._toolitems.setdefault(name, [])
self._add_to_group(group, name, button, position)
self._toolitems[name].append((button, handler))
add_tool
方法源码class ToolContainerBase:
# 省略部分代码
_icon_extension = '.png'
def add_tool(self, tool, group, position=-1):
tool = self.toolmanager.get_tool(tool)
image = self._get_image_filename(tool.image)
toggle = getattr(tool, 'toggled', None) is not None
self.add_toolitem(tool.name, group, position,
image, tool.description, toggle)
if toggle:
self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name,
self._tool_toggled_cbk)
# If initially toggled
if tool.toggled:
self.toggle_toolitem(tool.name, True)
def _get_image_filename(self, image):
"""Find the image based on its name."""
if not image:
return None
basedir = cbook._get_data_path("images")
for fname in [
image,
image + self._icon_extension,
str(basedir / image),
str(basedir / (image + self._icon_extension)),
]:
if os.path.isfile(fname):
return fname
ToolBase
、SaveFigureBase
、ToolQuit
类源码class ToolBase:
image = None
"""
Filename of the image.
**String**: Filename of the image to use in the toolbar. If None, the
*name* is used as a label in the toolbar button.
"""
class SaveFigureBase(ToolBase):
"""Base tool for figure saving."""
description = 'Save the figure'
image = 'filesave'
default_keymap = mpl.rcParams['keymap.save']
class ToolQuit(ToolBase):
"""Tool to call the figure manager destroy method."""
description = 'Quit the figure'
default_keymap = mpl.rcParams['keymap.quit']
def trigger(self, sender, event, data=None):
Gcf.destroy_fig(self.figure)