You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为带渐变色线条的误差棒图添加自定义渐变图例?

解决LineCollection渐变色线条的自定义图例问题

问题描述

我需要绘制两个带渐变色线条的误差棒图:一个线条颜色从粉色过渡到蓝色,另一个从蓝色过渡到粉色。由于plt.errorbar()无法实现渐变色线条效果,我通过LineCollection作为替代方案完成绘图:先绘制虚线样式的误差棒,再添加两段按所选colormap颜色范围着色的分段渐变色线条。但目前无法为这些渐变色线条创建自定义图例——尝试用matplotlib.lines.Line2D添加颜色块时,每个色块仅能指定单一颜色,求解决方法。

原绘图代码如下:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import BoundaryNorm, ListedColormap
from matplotlib.lines import Line2D

fig, axs = plt.subplots(1, 1, figsize=(10 , 8))

T = 9
x = np.linspace(0, T-1, T)
data = np.random.rand(T, 30)

# plot with error bars
Y_mean = np.mean(data, axis=1)     # mean for every row
Y_std = np.std(data, axis=1, ddof=1)   # std
plt.errorbar(x, Y_mean, yerr=Y_std, capsize=6, elinewidth=4, ecolor = "grey",linestyle = 'dotted',color='black')

# plot a line going from blue to pink
y = Y_mean
y_col = np.linspace(-10, 10, 8)
# Create a set of line segments so that we can color them individually
# This creates the points as an N x 1 x 2 array so that we can stack points
# together easily to get the segments. The segments array for line collection
# needs to be (numlines) x (points per line) x 2 (for x and y)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)

# Create a continuous norm to map from data points to colors
norm_cmap = plt.Normalize(y_col.min(), y_col.max())
lc = LineCollection(segments, cmap='cool', norm=norm_cmap)
# Set the values used for colormapping
lc.set_array(y_col)
lc.set_linewidth(4)
line = axs.add_collection(lc)

# plot the second plot with error bars
data = np.random.rand(T, 30) + 1
Y_mean = np.mean(data, axis=1)     # mean
Y_std = np.std(data, axis=1, ddof=1)   # std
plt.errorbar(x, Y_mean, yerr=Y_std, capsize=6, elinewidth=4, ecolor = "grey",linestyle = 'dotted',color='black')

# plot the second line
y = Y_mean
y_col = np.linspace(10, -10, 8) 
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
norm_cmap = plt.Normalize(y_col.min(), y_col.max())
lc = LineCollection(segments, cmap='cool', norm=norm_cmap)
lc.set_array(y_col)
lc.set_linewidth(4)
line = axs.add_collection(lc)

fig.suptitle("example plot")    

legend_elements = [Line2D([0], [0], color='black', linestyle = 'dotted', alpha = 1, label = r'line1', lw=4),
                  Line2D([0], [0], color='black', linestyle = 'dotted', alpha = 1, label = r'line2', lw=4)]
axs.legend(handles=legend_elements, bbox_to_anchor=(1.05, 1), loc="upper left", title_fontsize = 18);

解决方案

核心思路是创建支持渐变显示的自定义图例手柄,替代只能显示单一颜色的Line2D。这里提供两种可行方案:

方案1:用LineCollection生成渐变线条手柄

通过创建小型的LineCollection模拟渐变线条,再指定对应的图例处理器让matplotlib正确渲染:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.lines import Line2D
from matplotlib.legend_handler import HandlerLineCollection

fig, axs = plt.subplots(1, 1, figsize=(10 , 8))

T = 9
x = np.linspace(0, T-1, T)
data = np.random.rand(T, 30)

# 绘制第一个误差棒
Y_mean = np.mean(data, axis=1)
Y_std = np.std(data, axis=1, ddof=1)
plt.errorbar(x, Y_mean, yerr=Y_std, capsize=6, elinewidth=4, ecolor="grey", linestyle='dotted', color='black')

# 绘制第一个渐变色线条(蓝→粉)
y = Y_mean
y_col = np.linspace(-10, 10, 8)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
norm_cmap = plt.Normalize(y_col.min(), y_col.max())
lc1 = LineCollection(segments, cmap='cool', norm=norm_cmap)
lc1.set_array(y_col)
lc1.set_linewidth(4)
axs.add_collection(lc1)

# 绘制第二个误差棒
data = np.random.rand(T, 30) + 1
Y_mean = np.mean(data, axis=1)
Y_std = np.std(data, axis=1, ddof=1)
plt.errorbar(x, Y_mean, yerr=Y_std, capsize=6, elinewidth=4, ecolor="grey", linestyle='dotted', color='black')

# 绘制第二个渐变色线条(粉→蓝)
y = Y_mean
y_col = np.linspace(10, -10, 8)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc2 = LineCollection(segments, cmap='cool', norm=norm_cmap)
lc2.set_array(y_col)
lc2.set_linewidth(4)
axs.add_collection(lc2)

fig.suptitle("example plot")

# 定义创建渐变线条图例手柄的函数
def create_gradient_line_handle(cmap, norm, reverse=False, lw=4):
    # 创建隐藏的小坐标轴用于生成渐变线段
    handle_ax = fig.add_axes([0, 0, 0.1, 0.1])
    handle_ax.set_visible(False)
    # 生成渐变线段的坐标
    x_handle = np.linspace(0, 1, 10)
    y_handle = np.zeros_like(x_handle)
    points_handle = np.array([x_handle, y_handle]).T.reshape(-1, 1, 2)
    segments_handle = np.concatenate([points_handle[:-1], points_handle[1:]], axis=1)
    # 设置渐变颜色数组,reverse控制方向
    color_vals = np.linspace(norm.vmin, norm.vmax, len(segments_handle))
    if reverse:
        color_vals = color_vals[::-1]
    # 创建LineCollection作为手柄
    lc_handle = LineCollection(segments_handle, cmap=cmap, norm=norm)
    lc_handle.set_array(color_vals)
    lc_handle.set_linewidth(lw)
    handle_ax.add_collection(lc_handle)
    return lc_handle

# 创建两个渐变手柄
cmap = plt.get_cmap('cool')
norm = plt.Normalize(-10, 10)
handle_gradient1 = create_gradient_line_handle(cmap, norm, reverse=False)
handle_gradient2 = create_gradient_line_handle(cmap, norm, reverse=True)

# 组合所有图例元素
legend_elements = [
    Line2D([0], [0], color='black', linestyle='dotted', lw=4, label='曲线1'),
    handle_gradient1,
    Line2D([0], [0], color='black', linestyle='dotted', lw=4, label='曲线2'),
    handle_gradient2
]

# 绘制图例,指定HandlerLineCollection处理渐变线条手柄
axs.legend(handles=legend_elements,
           handler_map={LineCollection: HandlerLineCollection(numpoints=1)},
           bbox_to_anchor=(1.05, 1), loc="upper left", title_fontsize=18)

plt.tight_layout()
plt.show()

方案2:用AxesImage生成渐变色块手柄

通过生成小型的渐变图像作为图例手柄,实现更简洁的渐变显示:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.lines import Line2D
from matplotlib.image import AxesImage
from matplotlib.legend_handler import HandlerImage

fig, axs = plt.subplots(1, 1, figsize=(10 , 8))

T = 9
x = np.linspace(0, T-1, T)
data = np.random.rand(T, 30)

# 绘制第一个误差棒和渐变色线条
Y_mean = np.mean(data, axis=1)
Y_std = np.std(data, axis=1, ddof=1)
plt.errorbar(x, Y_mean, yerr=Y_std, capsize=6, elinewidth=4, ecolor="grey", linestyle='dotted', color='black')

y = Y_mean
y_col = np.linspace(-10, 10, 8)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
norm_cmap = plt.Normalize(y_col.min(), y_col.max())
lc1 = LineCollection(segments, cmap='cool', norm=norm_cmap)
lc1.set_array(y_col)
lc1.set_linewidth(4)
axs.add_collection(lc1)

# 绘制第二个误差棒和渐变色线条
data = np.random.rand(T, 30) + 1
Y_mean = np.mean(data, axis=1)
Y_std = np.std(data, axis=1, ddof=1)
plt.errorbar(x, Y_mean, yerr=Y_std, capsize=6, elinewidth=4, ecolor="grey", linestyle='dotted', color='black')

y = Y_mean
y_col = np.linspace(10, -10, 8)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc2 = LineCollection(segments, cmap='cool', norm=norm_cmap)
lc2.set_array(y_col)
lc2.set_linewidth(4)
axs.add_collection(lc2)

fig.suptitle("example plot")

# 定义创建渐变色块手柄的函数
def create_gradient_patch_handle(cmap, norm, reverse=False, size=(100, 20)):
    # 生成渐变数据
    gradient = np.linspace(norm.vmin, norm.vmax, size[0])
    if reverse:
        gradient = gradient[::-1]
    gradient = np.tile(gradient, (size[1], 1))
    # 创建AxesImage作为手柄
    img_handle = AxesImage(axs, cmap=cmap, norm=norm)
    img_handle.set_data(gradient)
    return img_handle

# 创建两个渐变色块手柄
cmap = plt.get_cmap('cool')
norm = plt.Normalize(-10, 10)
patch_gradient1 = create_gradient_patch_handle(cmap, norm, reverse=False)
patch_gradient2 = create_gradient_patch_handle(cmap, norm, reverse=True)

# 组合图例元素
legend_elements = [
    Line2D([0], [0], color='black', linestyle='dotted', lw=4, label='曲线1'),
    patch_gradient1,
    Line2D([0], [0], color='black', linestyle='dotted', lw=4, label='曲线2'),
    patch_gradient2
]

# 绘制图例,指定HandlerImage处理渐变色块手柄
axs.legend(handles=legend_elements,
           handler_map={AxesImage: HandlerImage(stretch=1)},
           bbox_to_anchor=(1.05, 1), loc="upper left", title_fontsize=18)

plt.tight_layout()
plt.show()

关键说明

  • 两种方案都通过自定义图例手柄解决了单一颜色的限制,分别模拟渐变线条和渐变色块的显示效果
  • 需要指定对应的图例处理器(HandlerLineCollection/HandlerImage),让matplotlib能够正确渲染非标准的图例元素
  • 可以根据需求调整渐变方向、线条宽度、色块尺寸等参数,匹配主图的视觉风格

内容的提问来源于stack exchange,提问作者Lidiia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 21:48:12