常见需求完整示例
下列所有函数均未预留更改样式的参数,需要在样式环境下使用(参考下文),以保持简洁,做到数据和样式分离。
先加入支持中文的代码,再自定义一个环境(可选),然后调用预先定义好的函数,就可以直接运行。
多系列折线图
def extend_lim(lim, extend):
if lim is None:
return None
minv, maxv = lim
if minv is None:
return (None, maxv * (1 + extend))
elif maxv is None:
return (minv * (1 - extend), None)
else:
section = maxv - minv
minv = minv - section * extend
maxv = maxv + section * extend
return (minv, maxv)
def plot(
data,
series,
*,
xlabel,
ylabel,
xlim,
ylim,
xinterval,
yinterval,
xlim_extend=0,
ylim_extend=0
):
fig, axe = plt.subplots(1, 1)
for legend, (xarray, yarray) in data.items():
axe.plot(xarray, yarray, label=legend)
axe.set(
xlim=extend_lim(xlim, xlim_extend),
ylim=extend_lim(ylim, ylim_extend),
xlabel=xlabel,
ylabel=ylabel,
)
axe.xaxis.set_major_locator(plt.MultipleLocator(xinterval))
axe.xaxis.set_minor_locator(plt.MultipleLocator(xinterval / 2))
axe.yaxis.set_major_locator(plt.MultipleLocator(yinterval))
axe.yaxis.set_minor_locator(plt.MultipleLocator(yinterval / 2))
if len(series) > 1:
axe.legend()
fig.tight_layout()
return fig
调用函数得到结果
import matplotlib.pyplot as plt
from cycler import cycler
import numpy as np
# 中文支持,必须写
plt.rcParams.update(
{
"font.sans-serif": "SimSun", # 中文支持,必须写
"axes.unicode_minus": False, # 中文支持,必须写
})
# 这个with块非必须
with plt.rc_context(rc=myenv):
fig = plot(
{
"菜场": [[0, 2, 4, 6], [4.0, 2.3, 3.1, 2.9]],
"超市": [[0, 2, 4, 6], [0.9, 2.1, 1.8, 1.2]],
"公园": [[0, 2, 4, 6], [1.6, 1.7, 1.2, 1.5]],
},
["公园", "菜场", "超市"],
xlabel="小时",
ylabel="人流量(千人)",
xlim=(0, 6),
xinterval=1,
xlim_extend=0.025,
ylim=(0, 4),
yinterval=0.5,
ylim_extend=0.025,
)
plt.show()
并列柱状图
class BarLoc(object):
def __init__(self, total_bar_width, series, data_len):
"""total_bar_width:同一组的不同色柱子的总宽度,范围0~1
series:图例列表
data_len:每个颜色的柱子有多少个数据"""
self.total_bar_width = total_bar_width
series_count = len(series)
self.series = dict(zip(series, np.arange(series_count)))
self.single_bar_width = total_bar_width / series_count
self.data_len = data_len
# ========
if series_count % 2 == 0:
start_num = -(series_count / 2 - 0.5) * self.single_bar_width
else:
start_num = -(series_count // 2) * self.single_bar_width
end_num = -start_num
self.first_bar_loc = np.linspace(start_num, end_num, series_count)
def iloc(self, index):
return np.arange(self.data_len) + self.first_bar_loc[index]
def loc(self, index):
return self.iloc(self.series[index])
def __getitem__(self, index):
return self.loc(index)
def parallel_bar(
data, series, xticklabels, total_bar_width, *, xlabel, ylabel, ylim, yinterval
):
fig, axe = plt.subplots(1, 1)
bar_loc = BarLoc(total_bar_width, series, len(xticklabels))
for legend, array in data.items():
axe.bar(bar_loc[legend], array, width=bar_loc.single_bar_width, label=legend)
axe.set_xticks(np.arange(len(xticklabels)), labels=xticklabels)
axe.set_ylim(ylim)
axe.yaxis.set_major_locator(plt.MultipleLocator(yinterval))
axe.yaxis.set_minor_locator(plt.MultipleLocator(yinterval / 2))
axe.set_xlabel(xlabel)
axe.set_ylabel(ylabel)
if len(series) > 1:
axe.legend()
fig.tight_layout()
return fig
调用函数得到结果:
import matplotlib.pyplot as plt
from cycler import cycler
import numpy as np
# 中文支持,必须写
plt.rcParams.update(
{
"font.sans-serif": "SimSun", # 中文支持,必须写
"axes.unicode_minus": False, # 中文支持,必须写
})
# 这个with块非必须
with plt.rc_context(rc=myenv):
parallel_bar(
{
"黑笔": [4.0, 2.3, 3.1, 2.9],
"蓝笔": [0.9, 2.1, 1.8, 1.2],
"红笔": [1.6, 1.7, 1.2, 1.5],
},
["黑笔", "红笔", "蓝笔"],
["旗舰店", "分店1", "分店2", "加盟店"],
0.72,
xlabel="店铺",
ylabel="销量(万只)",
ylim=(0, 4.25),
yinterval=0.5,
)
plt.show()
样式控制
设置对中文的支持
使用全局样式控制器plt.rcParams
,可采用字典式写法
import matplotlib.pyplot as plt
from cycler import cycler
import numpy as np
# 中文支持,必须写
plt.rcParams.update(
{
"font.sans-serif": "SimSun", # 中文支持,必须写
"axes.unicode_minus": False, # 中文支持,必须写
})
配置多个样式环境
matplotlib官网:自定义样式环境
推荐采用第一种方式,myenv作为dict对象,可以方便地进行组合,从而使不同的环境共享一部分设置
myenv = {
"figure.figsize": (4.8, 3.6), # 定义图片的长宽
# figure.figsize和dpi相乘就是图片长宽的具体像素值
"savefig.dpi": 200, # savefig.dpi是保存图片时的分辨率参数
"figure.dpi": 200, # figure.dpi是临时显示图片时的分辨率参数
# plot或scatter的每个系列的marker不一样
"axes.prop_cycle": cycler(
marker=["o", "^", "s", "D", "h", "x", "o", "^", "s", "D"],
color=plt.get_cmap("tab10")(np.arange(10)),
),
# 线粗和标记大小
"lines.linewidth": 1, # 默认值
"lines.markersize": 3, # 和上述分辨率配合较好
# 经验证明下述文字大小和上述分辨率配合较好
"legend.fontsize": 8,
"xtick.labelsize": 12,
"ytick.labelsize": 12,
"axes.labelsize": 12,
"mathtext.fontset": "stix", # 公式字体,此项不要修改
}
with plt.rc_context(myenv):
#进行绘图操作
#...
#注意plt.show()写在with里面,尽量把图片保存到磁盘
plt.show()
或在myrc.pltstyle
中输入配置,然后在代码中加入:
with plt.rc_context(fname="myrc.pltstyle"):
#进行绘图操作
#...
#注意plt.show()写在with里面,尽量把图片保存到磁盘
plt.show()
不太推荐使用文件式写法,因为有些值(如axes.prop_cycle
)右侧可能是复杂的函数,难以确定在文件中应该怎么写。实在需要的话可以参考matplotlib默认rc文件。
多系列图表的自动样式控制
cycler可以自动为多系列的图表的每个系列使用不同的颜色、标记形状等
创建cycler有两种写法,其中第二种写法只能指定一个键,一般用于含有特殊字符的键。
cycler可以相加,这保证了第二种写法也能写出有多个键的对象。
若cycler中指定了多个键,则每个键的列表长度必须相同。
cycler(aa=[...],bb=[...],...)
cycler("aa", [...])
最后,可以使用cycler.by_keys()
将其转化为dict和python其他数据结构进行耦合。
颜色设置
颜色表示方法
可以用形如#ab1295
的字符串表示单个颜色,["#aabbcc","#123456",...]
表示一组颜色。字符串还支持各种其他表示方法,请参阅matplotlib官网:颜色表示方法。
也可以用(R,G,B)
或(R,G,B,A)
及其ndarray形式表示单个颜色和一组颜色,这种方式一般是给程序内部使用的,不适合人工指定。
颜色组库
matplotlib提供了plt.get_cmap()
方法获取丰富的颜色库。颜色库中包含很多系列,每个系列都有对应的名称和样式预览。具体请参阅matplotlib官网:颜色库一览。
颜色系列分为两种,一种是由固定的N个颜色组成,使用方式为用形如plt.get_cmap("Accent")(np.arange(2,7))
的方式获取该颜色系列指定序号的颜色,或用形如plt.get_cmap("Accent")(4)
的方式获取一个颜色。
还有一种是渐变色,接收一个0~1之间的浮点数,使用方式为用形如plt.get_cmap("Blues")(np.linspace(0,1,n))
的方式获取n种“等距”的颜色。
诸如plt.get_cmap("Blues")
这样的函数返回的是一个类,因此后面加上圆括号用于构造类,不能望文生义写成方括号。
颜色组几乎没有什么对象属性,诸如颜色组包含哪些颜色,颜色数量等属性全部通过看图得出答案,可以将官网的颜色组库图下载保存,以备不时之需。
颜色组与cycler
可以使用cycler的color
键指定多系列图的颜色,方便在不同的环境中使用不同的系列颜色。
不能采用环境样式控制的项
图例
matplotlib官网:axes.legend
一般情况下,程序会自动找到一个不会阻挡图内容的位置放置图例,直接调用axe.legend()
即可。
若图内容占满了整张图,则需要把图例放到图外面:
axe.legend(bbox_to_anchor=(1, 1),loc="upper left")
参数说明:
- bbox_to_anchor:先假设图例框是一个点,决定图例框放在图像框的哪个位置。(0,0)是左下角,(1,1)是右上角,可以小于0或大于1
- loc:刚才设置的那个点,是图例框的哪一个点。先写上下,再写左右,中间有一个空格。
-上下:upper center lower
-左右:left center right
有时图例全部竖着排列太长,使其分列可以改变图例整体的形状
axe.legend(ncols=2)
网格
axe.grid(True, "both", "y", lw=0.5, ls=":",c="black")