Patches and path

形状(比如我们常见的箭头,正方形,椭圆等等)和路径(闭合或非闭合的点的连线,一条直线、曲线等都是“路径”,闭合的path则构成“形状”)

In [2]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib
from matplotlib import patches
from matplotlib.collections import PatchCollection
from matplotlib.path import Path
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
from PIL import Image
Using matplotlib backend: Qt5Agg

Patches

要获取全部形状,预先导入包matplotlib.patches,初始化一个形状实例,然后使用add_patch()或者add_artist()向axes中添加(单个)形状,或者使用PatchCollection对多个形状打包并用add_collection()向axes中添加这个包collection

In [7]:
### first demo, add a circle into axes
center=(2,2) # 圆心
radius=1     # 半径
# initial an object of circle
c1=patches.Circle(center,radius,fill=False,ec='r')
# plot, via add_patch()
ax=plt.gca()
ax.axis('equal')
ax.add_patch(c1)
ax.set_xlim(-2,8)
ax.set_ylim(-2,8) # 不起效?为什么y轴范围显示0-6?
### secend demo, add several patches into axes
center=(2,4.5)
points=np.array([4,0,7,1,5,4.2]).reshape(-1,2)
p1=patches.Polygon(points)
e1=patches.Ellipse(center,width=4,height=2,fill=False) # 不起效?不要填充!其它设置也都不行,我错在哪?请见本章最后
collection=PatchCollection([p1,e1])
ax.add_collection(collection)
for i in range(len(points)):
    plt.annotate('({0},{1})'.format(*points[i]),(points[i][0],points[i][1]))

Path

Tutorial
使用路径所经过的点集和每个点相应的命令代码code集作为参数构建Path实例对象,然后将其转化为PathPatch对象(patches.PathPatch),最后通过add_patch()或打包后add_collection()添加到axes中
path code: 代码 顶点数 描述 STOP 1(被忽略) 标志整个路径终点的标记(通常不需要) MOVETO 1 提起笔并移动到指定顶点 LINETO 1 从当前位置向指定顶点画线 CURVE3 2(一个起点、一个控制点、一个终点) 从当前位置,以给定控制点向给定端点画贝塞尔曲线 CURVE4 3(一个起点、两个控制点,一个终点) 从当前位置,以给定控制点向给定端点画三次贝塞尔曲线 CLOSEPOLY 1(被忽略) 向当前路径的起点画线
忽略的意思是指,譬如当前点坐标为(x,y),下一个点(坐标设为(c,d))被标记为Path.CLOSEPOLY,而路径起点坐标为(a,b)(且$a\ne c,b\ne d$),那么按照指令将从当前点出发直接来到起点,即(x,y)与(a,b)相连,(c,d)直接被忽略掉。标记为Path.STOP的点也会被忽略,也就是说,从当前点出发,但是下一个点对应的code是STOP,于是立即终结(在当前位置),根本就不会经过下一个点,通常也不需要指定这样的“终点”
CURVE3CURVE4指令用于绘制二阶和三阶的贝塞尔曲线

In [54]:
### first demo, draw Heart-curve with path code of 'CURVE3' or 'CURVE4'
# Path用形如(N,2)的(x,y)顶点数组和长为N的路径代码code数组进行实例化
points=[(0,0),(-0.8,1.2),(-1.6,0),(-1.8,-2),(-1.5,-3.2),(-0.7,-4.5),(0,-4.9)] # 左半平面点集
points+=[(-i[0],i[1]) for i in points][::-1][1:]
plt.subplot(121)
plt.plot(*(zip(*points)),marker='o',color='gray',alpha=0.3)
code=[Path.MOVETO]+[Path.CURVE4]*12
path=Path(points,code)
pathpatch=patches.PathPatch(path,fc='none',ec='g',linewidth=3)
ax=plt.gca()
ax.add_patch(pathpatch)
# 第二幅是我用自己写的Bessel曲线绘制函数绘制的心形图案,代码来自于bessel.py,假设你不在乎输出图案比较复杂造成干扰的话,可以直接使用接下来的“贝塞尔曲线”中的代码实验
plt.subplot(122)
image=Image.open(r'DB\image\36.png')
plt.imshow(image)
### 同时使用tight_layout()和subplots_adjust()协同布局,试着去掉其中一个,看看结果,可以发现这套组合技十分奏效
plt.tight_layout(rect=[0,0,2,2])
plt.subplots_adjust(bottom=0,top=1)
ax.plot([-1.8,1.8],[-2,-2],marker='o',color='r',linestyle='')
ax.annotate('这个棱角太突出了',(-1.7,-2),color='r')
plt.plot([290],[315],marker='o',color='r',linestyle='')
plt.annotate('这里太圆了,我希望下端尖一点',(300,320),color='r')
Out[54]:
Text(300,320,'这里太圆了,我希望下端尖一点')

上面的两个心形各有千秋,图右的圆润程度和ρ=a(1-cosθ)相比不遑多让,很明显CURVE4只能绘制三阶Bessel曲线,实在不够用,以至于图左分了四次Bessel曲线绘制,当然图左并非不行,可以稍微修改一下控制点的位置。图右的改进之处是,绘制两次Bessel,分左右两半部分绘制,就可以达到底端稍尖的效果:

In [135]:
### secend demo, use PatchCollection to add several PathPatch to axes
points1=[(1,1),(2,3.6),(3,4)]
points2=[(2,0),(3,1),(0,5),(1,4.5),(0,0),(0,1),(1,3),(1,2)]
code1=[Path.MOVETO,Path.LINETO,Path.LINETO]
code2=[Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO]
path1=Path(points1,code1)
path2=Path(points2,code2)
pathpatch1=patches.PathPatch(path1,fc='pink',ec='none',linewidth=3) # 又是这个问题,当试着打包patch进collection的时候,所有patch上的属性设置都不起效?
pathpatch2=patches.PathPatch(path2)
patchcollection=PatchCollection([pathpatch1,pathpatch2])
ax=plt.gca()
ax.add_collection(patchcollection)
plt.plot() # 添加pathpatch进axes,随后执行一次plot()便能显示图像,why? 原因很简单,默认axes轴域范围0-1,0-1,而图像在此区域之外,可以通过plt.axis()设置轴域显示范围解决,但我只想知道为什么plot()可以解决,且更加优美
#plt.axis([-0.5,3.5,-0.5,5.5])
Out[135]:
[]

贝塞尔曲线
另见bessel.py
参考 $P_0$,$P_1$,$P_2$是初始的三个点,分别是起点、控制点和终点,图中点标记的规则:$P_0$与$P_1$之间直线上的“T点”(下面会解释)首先记作$P_0$作为最终标记的一部分(取自前一个点$P_0$),然后再加上阶数,两个点绘制Bessel曲线是为1阶贝塞尔,于是最终记作$P_{0,1}$,再譬如$P_{0,1}$与$P_{1,1}$之间的“T点”,首先是$P_0$(取自前一个点标记$P_{0,1}$的前半部分),然后将逗号后的阶数自增1,得到$P_{0,2}$,以此类推,更高阶Bessel也是如此
Bessel曲线的特征十分容易掌握,对于任意的t来说,在每两个相邻点之间的直线上找到一个“T点”,姑且这么称呼吧,因为完全是t相关的嘛,记这两个点一前一后分别为A,B,其有$\frac{AT}{AB}=t$成立,其中AT表示A到T点的距离,AB表示A到B的距离(因此称其为“t点”或“T点”),于是对这n个点如此计算将得到n-1个“T点”,然后试图对这n-1个点绘制Bessel,重复上述操作,直至最后只剩下一个“T点”,这就是初始n个点在某一t下的Bessel曲线上的轨迹点
推导一下二阶下的Bessel方程:
由:$\frac{P_0P_{0,1}}{P_0P_1}=\frac{P_1P_{1,1}}{P_1P_2}=\frac{P_{0,1}P_{0,2}}{P_{0,1}P_{1,1}}=t$
有:$\frac{P_{0,1}-P_0}{P_1-P_0}=t$,$P_{0,1}=(1-t)P_0+tP_1$
有:$\begin{cases} P_{0,1}=(1-t)P_0+tP_1 \\ P_{1,1}=(1-t)P_1+tP_2 \\ P_{0,2}=(1-t)P_{0,1}+tP_{1,1} \end{cases}$
将前两个式子代入第三个:$P_{0,2}=(1-t)^2P_0+2t(1-t)P_1+t^2P_2$ (*)
其中$P_{0,2}$是轨迹点,$P_0$,$P_1$,$P_2$都是已知点,轨迹点设为$(x,y)$,三个已知点分别设为$(x_0,y_0)$,$(x_1,y_1)$,$(x_2,y_2)$,将这些点的x分量和y分量分别代入(*)式得到: $\begin{cases} x=(1-t)^2x_0+2t(1-t)x_1+t^2x_2 \\ y=(1-t)^2y_0+2t(1-t)y_1+t^2y_2 \end{cases}$

In [4]:
def fob(p1,p2,t):
    '''
    First-order Bessel, 一阶贝塞尔曲线,0个控制点,只有起点和终点
    取值t时,0<=t<=1,求此时的轨迹点
    '''
    #print(p1,p2,t)
    return ((1-t)*p1[0]+t*p2[0],(1-t)*p1[1]+t*p2[1])

def Track_point_gen(*points,accuracy=100):
    '''
    points: 贝塞尔曲线的起点、多个控制点和终点,a sequence of tuple point
    accuracy: 精细度,a scalar, 用于设置绘制的轨迹曲线上的点的个数,默认值100,请具名传递
    返回值:a list of historical tracked points , other process points and t
        返回一个列表,列表第一项是Bessel的历史轨迹点和当前轨迹点集合,列表第二项是绘制当前轨迹点的过程点集合
        列表第三项是当前的t值
    '''
    backup=points
    accuracy=100 if accuracy==None else int(np.fabs(accuracy))
    history=[points[0]] # 历史全部轨迹点集合
    for t in np.linspace(0,1,accuracy):
        others=[] # 用于绘制Bessel曲线的其它过程点集,即出图中非Bessel的其它点,称为“过程点”
        points=backup
        while len(points)>1:
            temp=[]
            for i,e in enumerate(points[1:],1):
                cur_point=fob(points[i-1],e,t) # current tracked point
                temp.append(cur_point)
            points=temp
            others.append(points)
        history.append(cur_point)
        yield [history,others,t]
        

def plot_bessel(*points,**kwargs):
    '''
    绘制贝塞尔曲线
    points: 两个端点和其它控制点
    kwargs: Line2D属性,和accuracy参数,且除accuracy参数以外全部传递给plot()函数
    '''
    accuracy=kwargs.get('accuracy')
    if accuracy!=None:
        del kwargs['accuracy']
    lock=False
    for i,j,t in Track_point_gen(*points,accuracy=accuracy):
        plt.cla()
        plt.title('Bessel曲线')
        plt.plot(*(zip(*points)),marker='o',linestyle='--',color='gray',alpha=0.5) # 两个端点和几个控制点构成的大致轮廓
        for k in points:
            plt.annotate('({0:.1f},{1:.1f})'.format(k[0],k[1]),k,alpha=0.5) # 点坐标
        for k in j:
            lines=plt.plot(*zip(*k),marker='o',alpha=0.3,markersize=3.5) # Bessel当前轨迹点的绘制过程
        bessel_line,*_=plt.plot(*zip(*i),**kwargs) # Bessel曲线
        plt.annotate('({0:.1f},{1:.1f})'.format(*j[-1][-1]),j[-1][-1]) # 当前轨迹点坐标
        plt.plot(*zip(points[0],j[-1][-1]),marker='o',color=bessel_line.get_color(),linestyle='') # 为贝塞尔曲线绘制两个端点
        if not lock:
            lock=True
            xlim,ylim=plt.gca().get_xlim(),plt.gca().get_ylim()
        plt.annotate('t={0:.2f}'.format(t),(xlim[1]-0.5,ylim[1]-0.2),bbox=dict(boxstyle="square",fc='pink'))
        plt.pause(0.1)

#plot_bessel((1,1),(4,3)) # 一阶
plot_bessel((1,1),(2,3),(3.2,1.1),accuracy=30,color='r') # 二阶
#plot_bessel((1,1),(0.7,4),(3.4,4.1),(5,1),(7,2.7),(9,2.6),(6,8.5),accuracy=60,color='b') # 六阶

Compound paths

复合路径,说白了就是在一条路径path内绘制多个图形,主要通过MOVETO提笔跳转指令,实现和构建多个path路径实例并打包进collection同样的效果
所有在 matplotlib,Rectangle,Circle,Polygon 等中的简单patch原语都是用简单的路径实现的。通过使用复合路径,通常可以更有效地实现绘制函数,如绘制直方图hist()和绘制条形图bar(),它们创建了许多原语,例如一堆Rectangle,这通常可使用复合路径来实现。bar创建一个矩形列表,而不是一个复合路径,很大程度上出于历史原因:路径代码是比较新的,bar在它之前就存在。虽然我们现在可以改变它,但它会破坏旧的代码,所以如果你需要为了效率,在你自己的代码中这样做,例如,创建动画条形图,在这里我们将介绍如何创建复合路径,帮助你实现并替换原始bar()
我们将通过为每个直方图创建一系列矩形,来创建直方图图表:矩形宽度是bin的宽度,矩形高度是该bin范围中的样本点数量。首先,我们将创建一些随机的正态分布数据并计算直方图(使用np.histogram())。因为 histogram 返回各个bin的边缘而不是中心,所以下面的示例中变量bins的长度比矩形数量n大1:

data = np.random.randn(1000) # 服从标准正态分布的随机变量X的总数为1000的样本数据
n, bins = np.histogram(data,100) # bin的个数为100,且有len(bins)=len(n)+1,其中数组n中是直方图的每一个bin的高度

现在我们已经可以很轻松地获取直方图每一个bin的位置和高度,也就可以获取每一个bin的四个顶点的位置坐标,这些位置坐标正是用于构建复合路径path的,教程中已给出了一种合理的方案,它获取每一个bin(一个矩形)的上下左右的位置,不得不说,这种方式很便宜(譬如全部矩形的左下角位置坐标就是(left,bottom),其余三个顶点类同):

nrects = len(n) # 矩形个数
left = bins[:-1]
right = bins[1:]
bottom = np.ones(nrects)*0 # 直方图每一个bin的bottom齐平,且设置为0,你也可以修改以抬高或者降低,但是会很难看
top = bottom+n

现在我们必须构造复合路径,它的指令集由每个矩形的一系列MOVETO,LINETO和CLOSEPOLY组成。对于每个矩形,我们需要 5 个顶点(先后顺序):一个代表MOVETO,三个代表LINETO,一个代表CLOSEPOLY。CLOSEPOLY对应的顶点被忽略,所以不用管它具体值为几何,实际上完全可以省略该点,即只需要 4 个点:

points = np.zeros((nrects*5,2)) # 初始points点集,其形状必须是(m,2),m指点集中点的总数
codes = np.ones(nrects*5, int) * Path.LINETO # 初始codes指令集
codes[::5] = Path.MOVETO # 起点为MOVETO,以间隔5为每一个矩形此起点值
codes[4::5] = Path.CLOSEPOLY
points[::5,0] = left # 为每个矩形的左下角顶点的x分量赋值
points[::5,1] = bottom # 为每个矩形的左下角顶点的y分量赋值
points[1::5,0] = left # 为每个矩形的左上角顶点的x分量赋值
points[1::5,1] = top # 为每个矩形的左上角顶点的y分量赋值
points[2::5,0] = right # 为每个矩形的右上角顶点的x分量赋值
points[2::5,1] = top # 为每个矩形的右上角顶点的y分量赋值
points[3::5,0] = right # 为每个矩形的右下角顶点的x分量赋值
points[3::5,1] = bottom # 为每个矩形的右下角顶点的y分量赋值

OK,现在添加到axes中即可:

path = Path(points,codes)
pathpatch = patches.PathPatch(path,fc='g')
ax = plt.gca()
ax.add_patch(pathpatch)

最后,不要忘记设置轴域显示范围:

#plt.axis([....]) # 具体值为多少视具体情况,更优美的做法应是:
plt.plot()

出图:

动态条形图

In [515]:
# an easy demo of bar, using compound path to plot
def plot_bar(*data_sets,xticks=None,width=None,xticks_labels=None,animation=False,accuracy=30,delay=5,show=False,labels=None):
    '''
    data_sets,数据集,可以在水平方向上绘制多个数据集的条形图,打包为元组的data_sets形状允许是(m,n)或(m,n,k),即二维或者三维数组,在参数传递的时候,以逗号相隔,传递多个数据集
    ,每个数据集的形状可以是一维或者二维,当它们被打包进元组,就变成上述的二维或三维了。譬如:plot_bar([1,2,3,4],[4,3,2,1]) 表示两个数据集,再譬如:
    plot_bar([[1,2,3,4],[2,3,4,5]],[[4,3,2,1],[5,4,3,2]]),还是两个数据集,但是每个数据集是二维的了,对于某个二维的数据集来说,不同行表示该数据集的变化,这个时候使用animation
    关键字参数,可以动画描述其变化,若animation为False,则只绘制数据集的第一行,其余行被忽略,特别地,plot([[1,2,3,4]],[[4,3,2,1]])使用animation后也能动态绘制,尽管每个数据集
    只有一行数据,但是plot([1,2,3,4],[4,3,2,1])即使置animation=True,也不能动画绘制,你应该明白具体的设定了,即传递的数据参数被打包进data_sets后,必须是三维的,否则animation无效
    xticks,数据集的x轴刻度,如果不设置(推荐),则由系统自动进行均匀布局,如果你指定了,它必须同data_sets具有完全相同的形状,且应当指定width,如果你不
    指定,系统会试图给定一个值,但是不能保证完美,假设你的刻度本身不是均匀的话,那么结果往往就不如人意
    xticks_labels,刻度标签,每一个bin都有具体的含义,该参数是必设置的,否则每个bin具体表示什么?xticks_labels和xticks参数的形状必须和data_sets(二维)的形状一致,对三维的
    data_sets,则形状应等同于:(data_sets.shape[0],data_sets.shape[-1]),xticks_labels和xticks不能是数组ndarray对象,允许是列表等序列,这并非技术性问题,仅仅是懒得再加一步处理
    另外,即使当数据集只有一个的时候,也应当想象存在多个数据集,即xticks_labels和xticks仍必须是二维序列,形如:[set1's ticks, set2's ticks],其中set1's ticks、set2's ticks是一个序列
    颜色,我没有给出颜色设置参数,系统为不同数据集指定不同的颜色,是一个在Set1(colormap)中的随机颜色映射,大概率不会发生重复,为了应对重复的情况,我还给bin添加了随机的
    edgecolor,如果不幸发生facecolor颜色一致的状况,可以通过edge颜色辨识,如果不满意,则重新执行函数
    animation,动画,当data_sets为形如(m,n,k)的三维数组时,animation奏效,默认为False
    accuracy,精度,设置动画的精细程度,调高该参数也可以减缓图像绘制速度,本函数在jupyter notebook中工作得很好,但需设置%matplotlib命令,否则出图嵌在网页内,若迁移到脚本中,还
    要添加plt.ion(),plt.ioff()
    delay,在绘制动画的时候,数据集发生下一次变化的时间间隔,间隔时间段主要用于观察当前时刻数据集
    show,是否显示每个bin的高度值,默认不显示
    labels,为每个数据集添加标签,其长度等于len(data_sets),相当于legend所起到的作用,它总是在轴域右上方绘制legend,该参数通常为必选参数,用来标识不同的数据集具体含义
    '''
    data_sets=np.array(data_sets)
    ymin,ymax=np.min(data_sets)-1,np.max(data_sets)+1
    nsets=len(data_sets) # 数据集的个数,多少个数据集,绘制多少个条形图,水平展示
    nrects=len(data_sets[0]) if data_sets.ndim==2 else len(data_sets[0][0]) if data_sets.ndim==3 else None # 条形图中bin的个数
    if nrects==None:
        raise ValueError("data_sets's shape must be (m,n) or (m,n,k)") # 允许传递的可变参数必须是形状为(m,)或(m,n)的序列或数组,它们被打包为元组将新增一个维度,并作为参数传递给np.array()
    
    # 刻度和标签设置
    if xticks==None:
        step=1/(nsets)
        width=step/2
        xticks=[]
        for i in range(nsets):
            xticks.append(np.arange(nrects)+step*i)
        xticks=np.array(xticks)
    else:
        xticks=np.array(xticks)
        if xticks.shape!=(data_sets.shape[0],data_sets.shape[-1]):
            raise ValueError("xticks's shape isn't lawful")
        if width==None:
            width=((xticks[1][0]-xticks[0][0]) if nsets>1 else (xticks[0][1]-xticks[0][0]))*(3/4)
    xticks_labels_flag=False
    if xticks_labels!=None:
        xticks_labels=np.array(xticks_labels)
        if xticks_labels.shape!=(data_sets.shape[0],data_sets.shape[-1]):
            print(xticks_labels,data_sets)
            raise ValueError("xticks_labels's shape isn't lawful")
        xticks_labels_flag=True
    xmin=min([min(i) for i in xticks])-width
    xmax=max([max(i) for i in xticks])+width
    plt.figure() # a new figure
    
    # 随机颜色映射
    facecolor=plt.cm.Set1(np.random.rand(nsets))
    edgecolor=plt.cm.prism(np.random.rand(nsets))
    
    def plot(sets):
        '''使用复合路径绘制诸多矩形'''
        # 变量俱取自外层嵌套函数,且除sets外,都保持不变
        plt.axis([xmin,xmax,ymin,ymax])
        plt.grid(axis='y')
        if ymin<0:
            plt.gca().spines['bottom'].set_position(('data',0))
            plt.axhline(plt.gca().get_ybound()[0],color='black')
        if xticks_labels_flag:
            plt.xticks(xticks.flatten(),xticks_labels.flatten())
        ax=plt.gca()
        # 显示legend
        if labels!=None:
            pos=[0.9,0.9,0.02,0.02]
            for i in range(nsets):
                plt.axes(pos,fc=facecolor[i]) ### 这里引起了一个警告!
                plt.xticks([])
                plt.yticks([])
                plt.annotate(labels[i],(plt.gca().get_xbound()[1],sum(plt.gca().get_ybound())/2),size=20)
                pos[1]-=0.025
            plt.sca(ax)
        for i,data in enumerate(sets):
            index=xticks[i]
            left=index-width/2
            right=left+width
            bottom=np.ones(nrects)*0
            top=bottom+data
            if show:
                for i2,j in enumerate(index):
                    plt.annotate('{0:.2f}'.format(top[i2]),(j,top[i2]))
            points=np.zeros((nrects*5,2))
            codes=np.ones(nrects*5, int)*Path.LINETO
            codes[::5]=Path.MOVETO
            codes[4::5]=Path.CLOSEPOLY
            points[::5,0]=left
            points[::5,1]=bottom
            points[1::5,0]=left
            points[1::5,1]=top
            points[2::5,0]=right
            points[2::5,1]=top
            points[3::5,0]=right
            points[3::5,1]=bottom
            path=Path(points,codes)
            pathpatch=patches.PathPatch(path,fc=facecolor[i],ec=edgecolor[i],lw=2,alpha=0.8)
            ax.add_patch(pathpatch)
    
    # 根据参数控制绘制行为
    if data_sets.ndim==2:
        plot(data_sets)
    if data_sets.ndim==3:
        if not animation:
            plot(data_sets[:,0,:])
        else: # 动画
            step=data_sets[:,0,:]/accuracy
            for i in range(accuracy):
                plt.cla()
                plot(i*step)
                plt.pause(0.05)
            plt.pause(delay)
            for i in range(data_sets.shape[1]-1):
                gap=data_sets[:,i+1,:]-data_sets[:,i,:]
                step=gap/accuracy
                for j in range(accuracy):
                    plt.cla()
                    plot(data_sets[:,i,:]+j*step)
                    plt.pause(0.05)
                plt.pause(delay)

# example
#plot_bar(np.random.randint(3,10,10),labels=['string'],xticks_labels=[['s'+str(i) for i in range(10)]],xticks=[[i for i in range(10)]])
#plot_bar(np.random.randint(3,10,10).reshape(1,-1),np.random.randint(3,10,10).reshape(1,-1),animation=True,show=True,labels=['man','woman'])
#plot_bar([1,3,2,4],[6,-2.3,5,7],xticks=[[1,2,3,4],[1.5,2.5,3.5,7]],width=0.4,xticks_labels=[['s1','s2','s3','s4'],['a1','a2','a3','a4']])
#plot_bar([[1,3,2,4],[0,4,1,4],[4,1,5,3]],[[6,2,5,7],[3,1,4,8],[2,7,6,0]],animation=True,delay=2,xticks_labels=[['优','良','中','及格']]*2,labels=['男生','女生'])
plot_bar([[1,2,3,4]],[[4,3,2,1]],animation=True,xticks_labels=[['s1','s2','s3','s4'],['a1','a2','a3','a4']])
# 试了好多例子,我想对于各个参数及其组合,我的函数应该没有什么错误了,假设有,恐怕以后我也无能为力了,因为忘记一干二净而且看不下去。。

关于将patch添加到collection之后丢失颜色等信息的问题
参见:Why is matplotlib.PatchCollection messing with color of the patches?
解决:在进行打包(构建PatchCollection实例)的时候,传递关键字match_original并置为True
By default patch collection over-rides the given color (doc) for the purposes of being able to apply a color map, cycle colors, etc. This is a collection level feature (and what powers the code behind scatter plot).
参见:PatchCollection
match_original参数:
If True, use the colors and linewidths of the original patches. If False, new colors may be assigned by providing the standard collection arguments, facecolor, edgecolor, linewidths, norm or cmap.

In [35]:
ax=plt.gca()
plt.axis('equal')
plt.axis([-1,5,-1,5])
w1=patches.Wedge((1,1),0.5,0,320,facecolor='red') # 320是320度的意思
w2=patches.Wedge((3,3),0.5,220,170)
bag=PatchCollection([w1,w2],match_original=True)
ax.add_collection(bag)
Out[35]:
<matplotlib.collections.PatchCollection at 0x16a43dc0c18>

Reference for matplotlib artists
官网提供了一个案例,绘制了常见的patch,并且给出了“艺术家”的传送门
This example displays several of matplotlib's graphics primitives (artists) drawn using matplotlib API. A full list of artists and the documentation is available at the artist API.

In [3]:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.lines as mlines
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection

def label(xy, text):
    y = xy[1] - 0.15  # shift y-value for label so that it's below the artist
    plt.text(xy[0], y, text, ha="center", family='sans-serif', size=14)

fig, ax = plt.subplots()
# create 3x3 grid to plot the artists
grid = np.mgrid[0.2:0.8:3j, 0.2:0.8:3j].reshape(2, -1).T

patches = []

# add a circle
circle = mpatches.Circle(grid[0], 0.1, ec="none")
patches.append(circle)
label(grid[0], "Circle")

# add a rectangle
rect = mpatches.Rectangle(grid[1] - [0.025, 0.05], 0.05, 0.1, ec="none")
patches.append(rect)
label(grid[1], "Rectangle")

# add a wedge
wedge = mpatches.Wedge(grid[2], 0.1, 30, 270, ec="none")
patches.append(wedge)
label(grid[2], "Wedge")

# add a Polygon
polygon = mpatches.RegularPolygon(grid[3], 5, 0.1)
patches.append(polygon)
label(grid[3], "Polygon")

# add an ellipse
ellipse = mpatches.Ellipse(grid[4], 0.2, 0.1)
patches.append(ellipse)
label(grid[4], "Ellipse")

# add an arrow
arrow = mpatches.Arrow(grid[5, 0] - 0.05, grid[5, 1] - 0.05, 0.1, 0.1,
                       width=0.1)
patches.append(arrow)
label(grid[5], "Arrow")

# add a path patch
Path = mpath.Path
path_data = [
    (Path.MOVETO, [0.018, -0.11]),
    (Path.CURVE4, [-0.031, -0.051]),
    (Path.CURVE4, [-0.115, 0.073]),
    (Path.CURVE4, [-0.03, 0.073]),
    (Path.LINETO, [-0.011, 0.039]),
    (Path.CURVE4, [0.043, 0.121]),
    (Path.CURVE4, [0.075, -0.005]),
    (Path.CURVE4, [0.035, -0.027]),
    (Path.CLOSEPOLY, [0.018, -0.11])]
codes, verts = zip(*path_data)
path = mpath.Path(verts + grid[6], codes)
patch = mpatches.PathPatch(path)
patches.append(patch)
label(grid[6], "PathPatch")

# add a fancy box
fancybox = mpatches.FancyBboxPatch(
    grid[7] - [0.025, 0.05], 0.05, 0.1,
    boxstyle=mpatches.BoxStyle("Round", pad=0.02))
patches.append(fancybox)
label(grid[7], "FancyBboxPatch")

# add a line
x, y = np.array([[-0.06, 0.0, 0.1], [0.05, -0.05, 0.05]])
line = mlines.Line2D(x + grid[8, 0], y + grid[8, 1], lw=5., alpha=0.3)
label(grid[8], "Line2D")

colors = np.linspace(0, 1, len(patches))
collection = PatchCollection(patches, cmap=plt.cm.hsv, alpha=0.3)
collection.set_array(np.array(colors))
ax.add_collection(collection)
ax.add_line(line)

plt.axis('equal')
plt.axis('off')
plt.tight_layout()

plt.show()