如何在Matplotlib中为条形图标签设置对应条形颜色?
问题描述
我经常需要制作带颜色编码的条形图,借助pyplot.bar_label可以轻松给条形添加标签,但让标签颜色和对应条形一致并不简单。
目前我实现的最优方式是:
fig, axe = plt.subplots() bars = axe.bar(["A", "B", "C"], height=[4, 6, 2], color=colors, label="Data") labels = axe.bar_label(bars, fontsize=10, fontweight=700, fmt="%.1f") for (label, color) in zip(labels, colors): label.set_color(color) axe.axhline(4, linestyle="-.", color="black", label="Limit") axe.legend()
但直觉上直接给bar_label传color=colors会报错:
labels = axe.bar_label(bars, fontsize=10, fontweight=700, fmt="%.1f", color=colors)
报错信息如下:
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In [4], line 3 1 fig, axe = plt.subplots() 2 bars = axe.bar(["A", "B", "C"], height=[4, 6, 2], color=colors, label="Data") ----> 3 labels = axe.bar_label(bars, fontsize=10, fontweight=700, fmt="%.1f", color=colors) 4 for (label, color) in zip(labels, colors): 5 label.set_color(color) File ~/.local/lib/python3.10/site-packages/matplotlib/axes/_axes.py:2678, in Axes.bar_label(self, container, labels, fmt, label_type, padding, **kwargs) 2675 if np.isnan(dat): 2676 lbl = '' -> 2678 annotation = self.annotate(fmt % value if lbl is None else lbl, 2679 xy, xytext, textcoords="offset points", 2680 ha=ha, va=va, **kwargs) 2681 annotations.append(annotation) 2683 return annotations File ~/.local/lib/python3.10/site-packages/matplotlib/axes/_axes.py:666, in Axes.annotate(self, text, xy, *args, **kwargs) 664 @docstring.dedent_interpd 665 def annotate(self, text, xy, *args, **kwargs): -> 666 a = mtext.Annotation(text, xy, *args, **kwargs) 667 a.set_transform(mtransforms.IdentityTransform()) 668 if 'clip_on' in kwargs: File ~/.local/lib/python3.10/site-packages/matplotlib/text.py:1825, in Annotation.__init__(self, text, xy, xytext, xycoords, textcoords, arrowprops, annotation_clip, **kwargs) 1822 self.arrow_patch = None 1824 # Must come last, as some kwargs may be propagated to arrow_patch. -> 1825 Text.__init__(self, x, y, text, **kwargs) File ~/.local/lib/python3.10/site-packages/matplotlib/text.py:143, in Text.__init__(self, x, y, text, color, verticalalignment, horizontalalignment, multialignment, fontproperties, rotation, linespacing, rotation_mode, usetex, wrap, transform_rotates_text, parse_math, **kwargs) 141 self._text = '' 142 self.set_text(text) -> 143 self.set_color( 144 color if color is not None else mpl.rcParams["text.color"]) 145 self.set_fontproperties(fontproperties) 146 self.set_usetex(usetex) File ~/.local/lib/python3.10/site-packages/matplotlib/text.py:947, in Text.set_color(self, color) 944 # "auto" is only supported by axisartist, but we can just let it error 945 # out at draw time for simplicity. 946 if not cbook._str_equal(color, "auto"): -> 947 mpl.colors._check_color_like(color=color) 948 # Make sure it is hashable, or get_prop_tup will fail (remove this once 949 # get_prop_tup is removed). 950 try: File ~/.local/lib/python3.10/site-packages/matplotlib/colors.py:130, in _check_color_like(**kwargs) 128 for k, v in kwargs.items(): 129 if not is_color_like(v): -> 130 raise ValueError(f"{v!r} is not a valid value for {k}") ValueError: ['orange', 'red', 'green'] is not a valid value for color
这说明bar_label的color参数仅支持单一颜色,请问有没有办法不用事后循环修改,直接在调用bar_label时设置每个标签对应条形的颜色?
解决方案
目前Matplotlib的bar_label方法本身不支持直接传入颜色列表为每个标签设置不同颜色,因为该方法会将color等关键字参数直接传递给annotate方法,而annotate的color仅接受单一颜色值。
你当前用循环逐个修改标签颜色的方式,是现阶段最直接有效的实现方法。不过可以优化写法,直接从条形对象中获取颜色,无需额外维护colors列表:
fig, axe = plt.subplots() bars = axe.bar(["A", "B", "C"], height=[4, 6, 2], color=["orange", "red", "green"], label="Data") labels = axe.bar_label(bars, fontsize=10, fontweight=700, fmt="%.1f") for label, bar in zip(labels, bars): label.set_color(bar.get_facecolor()) axe.axhline(4, linestyle="-.", color="black", label="Limit") axe.legend()
这种写法无需额外保存颜色列表,直接从每个条形实例提取填充色,更简洁且不易出错。
如果想要更“原生”的调用体验,可以自定义封装函数,把循环逻辑整合进去:
def bar_label_colored(ax, container, **kwargs): colors = kwargs.pop('colors', None) labels = ax.bar_label(container, **kwargs) if colors is None: colors = [bar.get_facecolor() for bar in container] for label, color in zip(labels, colors): label.set_color(color) return labels # 使用方式 fig, axe = plt.subplots() bars = axe.bar(["A", "B", "C"], height=[4, 6, 2], color=["orange", "red", "green"], label="Data") bar_label_colored(axe, bars, fontsize=10, fontweight=700, fmt="%.1f") axe.axhline(4, linestyle="-.", color="black", label="Limit") axe.legend()
后续调用时就不用再写循环,直接使用自定义函数即可。
内容的提问来源于stack exchange,提问作者jlandercy
相关产品推荐
相关产品推荐

