Python绘图】matplotlib:先搞明白plt. /ax./ fig再画
matplotlib approaches
采用matlotlib画图有多种方法,选择依据是代码是否容易维护。如果图像复杂,plt.xlabel,plt.ylabel,plt.title这些不能很好地定位对应的axis,相比之下面向对象的方式更容易修改和维护。
- pyplot API: state-based API,绘图操作类似Matlab。
- object-oriented API:object-oriented API,有复杂绘图需求时,matplotlib推荐直接操作对象。对象包括Figure,Axes等。
- pylab
pylab is a module that includes matplotlib.pyplot, numpy and some additional functions within a single namespace. Its original purpose was to mimic a MATLAB-like way of working by importing all functions into the global namespace. This is considered bad style nowadays.
matplotlib 术语
- canvas:
- Figure:上图红框,包含child Axes、少量 ‘special’ artists (titles, figure legends, x label,y label)、canvas
- Axes:上图蓝框,包含2个Axis对象、artists (titles, figure legends, x label,y label)
- Axis:上图绿框中2个像数轴一样的对象(分别对应Axes.xaxis和Axes.yaxis),包含数据极值、ticks和ticklabels。
Axes
Axes就是我们常用的fig,ax = plt.subplots()中的ax。从官方文档axes_api中可以看出它的重要性,可以完成画图、设置Appearance/label/legend/ticks/ticklabels等。
以坐标轴刻度设置为例:细调坐标轴
Axes.tick_params(axis=‘both’, **kwargs)
参数:
axis : {‘x’, ‘y’, ‘both’} Axis on which to operate; default is ‘both’.
reset : bool If True, set all parameters to defaults before processing other keyword arguments. Default is False.
which : {‘major’, ‘minor’, ‘both’} Default is ‘major’; apply arguments to which ticks.
direction : {‘in’, ‘out’, ‘inout’} Puts ticks inside the axes, outside the axes, or both.
length : float Tick length in points.
width : float Tick width in points.
color : color Tick color; accepts any mpl color spec.
pad : float Distance in points between tick and label.
labelsize : float or str Tick label font size in points or as a string (e.g., ‘large’).
labelcolor : color Tick label color; mpl color spec.
colors : color Changes the tick color and the label color to the same value: mpl color spec.
zorder : float Tick and label zorder.
bottom, top, left, right : bool or {‘on’, ‘off’} controls whether to draw the respective ticks.
labelbottom, labeltop, labelleft, labelright : bool or {‘on’, ‘off’} controls whether to draw the respective tick labels.
labelrotation : float Tick label rotation
x轴ticks、tickslabel旋转30°,写在上方的方法:
import matplotlib.pyplot as plt
import numpy as np
fig,ax = plt.subplots()
plt.plot(np.random.rand(10))
ax.tick_params(axis='x', bottom=False, top=True, labelbottom=False, labeltop=True, labelrotation=30)
plt.show()
tkinter+ pyplot API无法退出进程
tkinter做界面时, pyplot API画图不关闭主界面直接退出mainGUI时会无法退出进程(感兴趣可以尝试选择method1或method2取消注释前后直接关闭主界面的差别)。解决方案是plt.close()或者退出时采用root.quit()杀死全部进程;另一种就是避免采用 pyplot API,选择面向对象的方式画图。
import numpy as np
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root = tk.Tk()
fig = plt.figure()
plt.plot(np.random.rand(10))
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
# # method 1
# def quit():
# plt.close()
# root.destroy()
# root.protocol('WM_DELETE_WINDOW', quit) #退出前先关闭plt
#
# # method 2
# root.protocol('WM_DELETE_WINDOW', root.quit) #退出前杀死全部进程。
root.mainloop()
多轴绘制
- 主轴与寄生轴。即mpl_toolkits.axisartist.parasite_axes里的HostAxes,和ParasiteAxes。
- twin axis+轴的偏移
- mpl_toolkits.axes_grid1.parasite_axes’ host_subplot+ mpl_toolkits.axisartist.axislines.Axes
Event handling and picking
Event connections
为了接收事件,需要写回调函数并绑定在事件管理器(FigureCanvasBase的一部分)上。如下所示是将onclick回调函数绑定在按钮按压事件上:
fig, ax = plt.subplots()
ax.plot(np.random.rand(10))
def onclick(event):
print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
('double' if event.dblclick else 'single', event.button,
event.x, event.y, event.xdata, event.ydata))
cid = fig.canvas.mpl_connect('button_press_event', onclick)
interactive navigation的改写【home,back,forward,zoom,save】
plt时toolbar自动创建,如果是自己写user interface code,可以把toolbar作为widget添加
- NTbar是基于NavigationToolbar2Tk的toolbar组件,去掉了修改subplots参数的按钮,这里只是为了展示继承的方法。
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
class NTbar(NavigationToolbar2Tk):
'''toolbar组件'''
def __init__(self, canvas, window,btnname):
self.canvas = canvas
self.window = window
NavigationToolbar2Tk.__init__(self, canvas,window)
self.children['!button6'].pack_forget() #无法修改,去掉
self.window.config(text='%s画布区 第1张 / 共1张' % btnname)
# def forward(self, *args):
# """Move forward in the view lim stack."""
# self._nav_stack.forward()
# self.set_history_buttons()
# self._update_view()
#
# def home(self, *args):
# """Restore the original view."""
# self._nav_stack.home()
# self.set_history_buttons()
# self._update_view()
#
# def back(self, *args):
# """move back up the view lim stack"""
# self._nav_stack.back()
# self.set_history_buttons()
# self._update_view()
- CustomToolbar是基于NavigationToolbar2Tk的toolbar组件,
- 去掉了修改subplots参数的按钮
- 将forward、back函数改成了多张图片的前后切换,home改为回到第一张图
- 增加了滑条重定向特定序号图片的功能
- 增加了批量保存图片到本地的功能
import os
from ttkwidgets import TickScale
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox
from tkinter.filedialog import askdirectory
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
class CustomToolbar(NavigationToolbar2Tk):
'''用于除日期可视化外的其他图片的toolbar组件'''
def __init__(self, canvas, window, btnname,fignum,pic_plot,logger=None,
figpath=os.getcwd()):
self.toolitems = [('Home', 'Lorem ipsum dolor sit amet', 'home', 'home'),
('Back', 'consectetuer adipiscing elit', 'back', 'back'),
('Forward', 'sed diam nonummy nibh euismod', 'forward', 'forward'),
(None, None, None, None),
('Pan', 'tincidunt ut laoreet', 'move', 'pan'),
('Zoom', 'dolore magna aliquam', 'zoom_to_rect', 'zoom'),
(None, None, None, None),
('Subplots', 'putamus parum claram', 'subplots', 'configure_subplots'),
('Save', 'sollemnes in futurum', 'filesave', 'save_figure')]
# self.canvas = canvas
# self.window = window
self.toggle = 0
self.fignum = fignum
if self.fignum > 10:
self.toolitems.extend([(None, None, None, None),
('BatchSave', 'Batch save', "filesave_large", 'batch_save'),
('Redirect', 'redirect to specific fig', "hand", 'redirect')])
NavigationToolbar2Tk.__init__(self, canvas, window)
self.btnname = btnname
self.pic_plot = pic_plot
self.children['!button6'].pack_forget() # 无法修改,去掉
self.logger = logger
self.figpath = figpath
def base_draw(self,toggle):
self.window.config(text='%s画布区 第%d张 / 共%d张' % (self.btnname, toggle + 1, self.fignum))
self.pic_plot(toggle)
def pan(self):
"""Restore the original view."""
self._nav_stack.home()
self.set_history_buttons()
self._update_view()
def home(self):
'''切回首页'''
self.toggle = 0
self.base_draw(self.toggle)
def forward(self):
'''向前查看'''
self.toggle += 1
if self.toggle > (self.fignum - 1):
self.toggle = self.fignum - 1
return
self.base_draw(self.toggle)
def back(self):
'''向后查看'''
self.toggle -= 1
if self.toggle < 0:
self.toggle = 0
return
self.base_draw(self.toggle)
def batch_save(self):
print("You clicked the selection tool")
result = tkinter.messagebox.askyesno("批量保存图片", "图片数量较多,批量存储耗时较长,是否继续?")
if result:
try:
# This method will handle the delegation to the correct type
path_ = askdirectory()
self.figpath_ = os.path.join(path_, self.btnname)
self.mkfigdir(self.figpath_)
for ii in range(self.fignum):
self.base_draw(ii)
figname = '%s_%s_%s.jpeg' % (self.btnname, ii, date.today())
self.canvas.figure.savefig(os.path.join(self.figpath_, figname))
if self.logger:
self.logger.info('此次图片保存路径:%s' % self.figpath_)
except Exception as e:
if self.logger:
self.logger.error("Error saving file"+str(e))
def redirect(self):
print('redirect')
top = tkinter.Toplevel(self.window)
s2 = TickScale(top, orient='horizontal', from_=1, to=self.fignum, tickinterval=5, resolution=1,
showvalue=True, length=400)#style='my.Horizontal.TScale',
s2.pack(fill='x')
def print_sel():
asd = int(s2.get())
print(asd)
if asd < self.toggle:
self.toggle = asd
self.back()
elif asd == self.toggle:
self.back()
else:
self.toggle = asd - 2
self.forward()
# s2.bind("<ButtonRelease>", print_sel)
ttk.Button(master=top,text='转到指定页',command=print_sel).pack()
def mkfigdir(self,path):
isExists = os.path.exists(path)
# 判断结果
if not isExists:
# 如果不存在则创建目录
# 创建目录操作函数
os.makedirs(path)