[ Matplotlib] 阿里Qwen风格柱状图绘制

效果

image

代码

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
from matplotlib.ticker import FixedLocator, FixedFormatter

# 数据
models = ['ProGraphTrans', 'Element-wise Addition', 'Concatenation', 'ESM2']
benchmarks = ['Acc', 'Sen', 'Spe', 'MCC']


data = [
    [88.9, 90.6, 87.2, 77.8],
    [84.1, 89.0, 79.2, 68.5],
    [85.7, 88.7, 82.7, 71.6],
    [82.1, 93.5, 70.7, 65.9]
]

# 颜色值
colors = ['#dd3005', '#5c6bf8', '#d3d3d3', '#ded1a2']

# 创建图表
fig, ax = plt.subplots(figsize=(10, 8))  # 设置图表尺寸为 12 英寸宽、8 英寸高

# ==================== 可调节参数 ====================
legend_y_offset = 1.15  # 控制图例垂直位置(越大距离柱状图越远)
legend_shift = -0.05  # 控制图例和矩形框整体下移的距离(负值表示下移)
rect_padding = 0.02  # 矩形框内边距
rect_linewidth = 1.5  # 矩形框线宽
rect_color = '#d3d3d3'  # 矩形框颜色(浅灰色)

# 自定义空白长度(单位为英寸)
top_margin_inch = 1.5  # 图表顶部留白
bottom_margin_inch = 1.0  # 图表底部留白
left_margin_inch = 1.0  # 图表左侧留白
right_margin_inch = 1.0  # 图表右侧留白

# 获取当前图表的总宽度和高度(单位为英寸)
fig_width_inch, fig_height_inch = fig.get_size_inches()

# 计算比例
top_margin_ratio = top_margin_inch / fig_height_inch
bottom_margin_ratio = bottom_margin_inch / fig_height_inch
left_margin_ratio = left_margin_inch / fig_width_inch
right_margin_ratio = right_margin_inch / fig_width_inch

# 自定义 y 轴范围
y_min = 0  # y 轴最小值
y_max = 100  # y 轴最大值
# ==================================================

width = 0.12
gap_within_group = 0.01
gap_between_groups = 0.15
x = range(len(benchmarks))
offset = 0.15

# 计算 x 轴范围
left_edge = -offset
right_edge = len(benchmarks) - 1 + 2 * offset + gap_between_groups * (len(benchmarks) - 1)
ax.set_xlim(left_edge, right_edge)

# 定义渐变色函数
def gradient_color(height, color_top="#dd3005", color_bottom="#f8e9d0"):
    gradient = np.linspace(0, 1, 256).reshape(-1, 1)
    gradient = np.hstack((gradient, gradient))
    cmap = plt.cm.colors.LinearSegmentedColormap.from_list("gradient", [color_bottom, color_top])
    return cmap(gradient)

# 绘制柱状图
for i in range(len(models)):
    positions = [xi + offset + (width + gap_within_group) * i for xi in x]

    if i == 0:
        for j, pos in enumerate(positions):
            height = data[i][j]
            ax.imshow(gradient_color(height),
                      extent=[pos - width / 2, pos + width / 2, 0, height],
                      aspect="auto", origin="lower")
    else:
        ax.bar(positions, data[i], width, color=colors[i])

    for j, pos in enumerate(positions):
        height = data[i][j]
        ax.text(pos, height, '%.1f' % data[i][j],
                ha='center', va='bottom', fontsize=9,
                color='red' if i == 0 else 'black',
                fontweight='bold' if i == 0 else 'normal')

# 设置轴标签和样式
ax.set_xticks([p + offset + (width + gap_within_group) * (len(models) - 1) / 2 for p in x])
ax.set_xticklabels(benchmarks, fontsize=14, fontweight='bold')
# ax.set_ylabel('Scores', fontsize=14, fontweight='bold')

# 设置 y 轴范围
ax.set_ylim(y_min, y_max)

# 设置 y 轴数字加粗
y_ticks = ax.get_yticks()  # 获取当前 y 轴刻度值
y_tick_labels = [f'{int(tick)}' for tick in y_ticks]  # 格式化刻度标签

# 使用 FixedLocator 和 FixedFormatter
ax.yaxis.set_major_locator(FixedLocator(y_ticks))  # 设置刻度位置
ax.yaxis.set_major_formatter(FixedFormatter(y_tick_labels))  # 设置刻度标签

for label in ax.get_yticklabels():
    label.set_fontsize(12)  # 设置字体大小
    label.set_fontweight('bold')  # 设置字体加粗

for spine in ax.spines.values():
    spine.set_color('#d3d3d3')
    spine.set_linewidth(1.0)
ax.tick_params(axis='both', which='both', length=0)


legend_elements = [Rectangle((0, 0), 1, 1, color=c, label=m)
                   for m, c in zip(models, colors)]
legend = ax.legend(handles=legend_elements,
                   loc='upper center',
                   bbox_to_anchor=(0.5, legend_y_offset + legend_shift),  # 放置在柱状图上方
                   ncol=len(models),
                   fontsize=15,
                   prop={'weight': 'bold'},  # 设置图例文本加粗
                   frameon=False,
                   columnspacing=4.4,  # 增大列间距以均匀分布
                   handletextpad=0.5,  # 图例标记与文本之间的间距
                   handlelength=1.5)  # 图例标记长度

# 获取图例的边界框
fig.canvas.draw()
legend_bbox = legend.get_tightbbox(fig.canvas.get_renderer())
legend_bbox = legend_bbox.transformed(fig.transFigure.inverted())


# 动态计算矩形框的宽度和高度
rect_x = 0.5 - (legend_bbox.width / 2 + rect_padding)  # 水平居中
rect_width = legend_bbox.width + 2 * rect_padding  # 矩形框宽度
rect_y = legend_bbox.ymin - legend_bbox.height-2.5*rect_padding  # 垂直居中
rect_height =  2 * rect_padding  # 矩形框高度

# 绘制矩形框
rect = Rectangle((rect_x, rect_y),
                 rect_width,
                 rect_height,
                 transform=fig.transFigure,
                 edgecolor=rect_color,  # 浅灰色
                 facecolor='none',
                 linewidth=rect_linewidth,  # 加粗
                 clip_on=False)
fig.patches.append(rect)

# 自定义左右上下留白距离
plt.subplots_adjust(
    top=1 - top_margin_ratio,  # 上方留白
    bottom=bottom_margin_ratio,  # 下方留白
    left=left_margin_ratio,  # 左侧留白
    right=1 - right_margin_ratio  # 右侧留白
)


plt.savefig('Figs/struct_aware.pdf', dpi=300, bbox_inches='tight')  # 设置 dpi=100,确保分辨率为 1200x800
plt.show()
posted @ 2025-08-29 16:08  ylifs  阅读(21)  评论(0)    收藏  举报