plt.bar
用matplotlib画bar图是可以给柱子加花纹的,叫做hatch,指定hatch
参数就可以。但是有一个问题,hatch的颜色默认是跟edgecolor
一致的,其中edgecolor
也是一个参数,可以指定柱子边框的颜色。hatch的颜色没有一个参数可以单独指定。
这会造成一些麻烦以及不灵活。比如我就是想用黑色的柱子边框,但是花纹并不想用黑色的。那怎么办呢?前人有一些方法:
这个答主是通过画两层柱子实现,底下的一层指定hatch和你喜欢的hatch颜色(设置edgecolor),上面的一层只画一个透明的黑色边框柱子(设置edgecolor为黑色)。画出来的图就可以满足要求。
但是问题来了。用这种方法画出来的柱子,本质上是两根柱子,如果你要画legend,你的legend要么就是有花纹而且跟花纹同色的边框,要么就是透明黑框。也就是说,你没有办法画一个跟柱子一模一样的legend。
勉强让人能接受的做法就是——放弃legend的黑框。legend没有黑框也没有关系对吧,小问题。要不然就不要画hatch咯。
那有没有一种灵活的办法呢?不好意思,官方说没有。
There is no direct API level control of the hatch color or linewidth.
Changes to the default style
那强迫症只能自己动手了。matplotlib相关的源代码是在
定位一下相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class Patch(artist.Artist): # ... def __init__(self, edgecolor=None, facecolor=None, color=None, linewidth=None, linestyle=None, antialiased=None, hatch=None, fill=True, capstyle=None, joinstyle=None, **kwargs): # ... self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color']) self._fill = True # needed for set_facecolor call if color is not None: if edgecolor is not None or facecolor is not None: _api.warn_external( "Setting the 'color' property will override " "the edgecolor or facecolor properties.") self.set_color(color) else: self.set_edgecolor(edgecolor) self.set_facecolor(facecolor) # ... self.set_hatch(hatch) self.set_capstyle(capstyle) self.set_joinstyle(joinstyle) # ... |
可见逻辑是设置默认的self._hatch_color
为mpl.rcParams['hatch.color']
。然后如果设置了edgecolor
参数就会调用self.set_edgecolor(edgecolor)
更新参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def _set_edgecolor(self, color): set_hatch_color = True if color is None: if (mpl.rcParams['patch.force_edgecolor'] or not self._fill or self._edge_default): color = mpl.rcParams['patch.edgecolor'] else: color = 'none' set_hatch_color = False self._edgecolor = colors.to_rgba(color, self._alpha) if set_hatch_color: self._hatch_color = self._edgecolor self.stale = True def set_edgecolor(self, color): """ Set the patch edge color. Parameters ---------- color : color or None """ self._original_edgecolor = color self._set_edgecolor(color) |
可以最终将问题定位在self._set_edgecolor()
这个函数里面。逻辑是如果不设定edgecolor
,那他就会去找mpl.rcParams
里面的默认参数,默认参数都没有的话就不改变self._hatch_color
(也就是说依然是默认的mpl.rcParams['hatch.color']
)。
如果设置了edgecolor
,那他就把self._hatch_color
也设置成self._edgecolor
。这就是为什么默认hatch的颜色默认是跟edgecolor
一致的。
我用了一种笨拙的方法去单独设置self._hatch_color
,因为没有时间去折腾一个更优雅的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def _set_edgecolor(self, color): set_hatch_color = True if color is None: if (mpl.rcParams['patch.force_edgecolor'] or not self._fill or self._edge_default): color = mpl.rcParams['patch.edgecolor'] else: color = 'none' set_hatch_color = False self._edgecolor = colors.to_rgba(color, self._alpha) # if set_hatch_color: # self._hatch_color = self._edgecolor self.stale = True |
对…只是直接把默认赋值给注释掉,然后画bar图之前设置mpl.rcParams['hatch.color']
作为hatch颜色就好啦。
e.g.
1 2 3 |
mpl.rcParams['hatch.color'] = edgecolors[i] tmp = ax.bar(S - ((num-1)/2-i)*width, metrics[i], width, label=labels[i], color = colors[i], edgecolor = 'k', lw=0.8, hatch='X', zorder=i) |
Done.