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

柱状图旋转数值标签未居中问题及解决需求

解决Matplotlib旋转90度的柱状图数值标签居中对齐问题

问题说明

使用Matplotlib绘制双柱状图时,为每个柱状顶部添加旋转90度的数值标签,设置ha='center'后,标签并未与柱状顶部中心对齐,需要实现旋转后数值的中点与柱状顶部中心对齐。

原代码

import matplotlib.pyplot as plt
import numpy as np
import random

def plot_relative_undercover_dual(ls1, ls2, demand_dict, days, shifts):
    list1, list2 = [], []

    for day in range(1, days + 1):
        daily_sum1 = sum(ls1.get((day, shift), 0) for shift in range(1, shifts + 1))
        daily_sum2 = sum(ls2.get((day, shift), 0) for shift in range(1, shifts + 1))
        daily_demand_sum = sum(demand_dict.get((day, shift), 0) for shift in range(1, shifts + 1))

        if daily_demand_sum > 0:
            relative1 = daily_sum1 / daily_demand_sum
            relative2 = daily_sum2 / daily_demand_sum
        else:
            relative1 = relative2 = 0

        list1.append(relative1)
        list2.append(relative2)

    plt.figure(figsize=(12, 6))

    x = np.arange(1, days + 1)
    width = 0.35

    colors = plt.cm.magma([0.8, 0.2])

    bars1 = plt.bar(x - width / 2, list1, width, color=colors[0], alpha=0.7, label='Option 1')
    bars2 = plt.bar(x + width / 2, list2, width, color=colors[1], alpha=0.7, label='Option 2')

    plt.xlabel('Days', fontsize=14.5, labelpad=15)
    plt.ylabel('Relative Costs', fontsize=14.5, labelpad=15)

    plt.grid(axis='y', linestyle='--', alpha=0.7)
    plt.xticks(x)

    def add_value_labels(bars):
        for bar in bars:
            height = bar.get_height()
            plt.text(bar.get_x() + bar.get_width() / 2., height + 0.01,
                     f'{height:.2%}',
                     ha='center', va='bottom', rotation=90, fontsize=10, color='black')

    add_value_labels(bars1)
    add_value_labels(bars2)

    # 找到所有柱状图的最大高度
    max_height = max(max(bar.get_height() for bar in bars1), max(bar.get_height() for bar in bars2))

    # 为标签和图例在最高柱状图上方添加边距
    legend_margin = 0.15 * max_height
    plt.ylim(top=max_height + legend_margin)

    # 创建带细边框的图例
    legend = plt.legend(loc='upper left', bbox_to_anchor=(0.02, 0.98), ncol=1, frameon=True, edgecolor='black', facecolor='white', framealpha=1, fontsize=14.5)
    legend.get_frame().set_linewidth(0.5)

    plt.tight_layout()
    plt.show()


def generate_random_data(days, shifts, min_value, max_value):
    return {(day, shift): round(random.uniform(min_value, max_value), 2) for day in range(1, days + 1) for shift in range(1, shifts + 1)}

demand_dict, u1, u2 = generate_random_data(28, 3, 0, 105), generate_random_data(28, 3, 0, 100), generate_random_data(28, 3, 0, 100)

plot_relative_undercover_dual(u1, u2, demand_dict, 28, 3)

解决方案

问题核心是文本旋转时的锚点和对齐方式设置错误。修改add_value_labels函数中的plt.text参数即可解决:

  • 将va='bottom'改为va='center',让文本垂直中点对齐锚点
  • 添加rotation_mode='anchor',确保旋转围绕锚点(柱状顶部中心)进行
  • 微调y轴偏移量,避免标签与柱状顶部完全重叠

修改后的add_value_labels函数:

def add_value_labels(bars):
    for bar in bars:
        height = bar.get_height()
        plt.text(bar.get_x() + bar.get_width() / 2., height + 0.005,
                 f'{height:.2%}',
                 ha='center', va='center', rotation=90, rotation_mode='anchor',
                 fontsize=10, color='black')

完整修改后代码

import matplotlib.pyplot as plt
import numpy as np
import random

def plot_relative_undercover_dual(ls1, ls2, demand_dict, days, shifts):
    list1, list2 = [], []

    for day in range(1, days + 1):
        daily_sum1 = sum(ls1.get((day, shift), 0) for shift in range(1, shifts + 1))
        daily_sum2 = sum(ls2.get((day, shift), 0) for shift in range(1, shifts + 1))
        daily_demand_sum = sum(demand_dict.get((day, shift), 0) for shift in range(1, shifts + 1))

        if daily_demand_sum > 0:
            relative1 = daily_sum1 / daily_demand_sum
            relative2 = daily_sum2 / daily_demand_sum
        else:
            relative1 = relative2 = 0

        list1.append(relative1)
        list2.append(relative2)

    plt.figure(figsize=(12, 6))

    x = np.arange(1, days + 1)
    width = 0.35

    colors = plt.cm.magma([0.8, 0.2])

    bars1 = plt.bar(x - width / 2, list1, width, color=colors[0], alpha=0.7, label='Option 1')
    bars2 = plt.bar(x + width / 2, list2, width, color=colors[1], alpha=0.7, label='Option 2')

    plt.xlabel('Days', fontsize=14.5, labelpad=15)
    plt.ylabel('Relative Costs', fontsize=14.5, labelpad=15)

    plt.grid(axis='y', linestyle='--', alpha=0.7)
    plt.xticks(x)

    def add_value_labels(bars):
        for bar in bars:
            height = bar.get_height()
            plt.text(bar.get_x() + bar.get_width() / 2., height + 0.005,
                     f'{height:.2%}',
                     ha='center', va='center', rotation=90, rotation_mode='anchor',
                     fontsize=10, color='black')

    add_value_labels(bars1)
    add_value_labels(bars2)

    # 找到所有柱状图的最大高度
    max_height = max(max(bar.get_height() for bar in bars1), max(bar.get_height() for bar in bars2))

    # 为标签和图例在最高柱状图上方添加边距
    legend_margin = 0.15 * max_height
    plt.ylim(top=max_height + legend_margin)

    # 创建带细边框的图例
    legend = plt.legend(loc='upper left', bbox_to_anchor=(0.02, 0.98), ncol=1, frameon=True, edgecolor='black', facecolor='white', framealpha=1, fontsize=14.5)
    legend.get_frame().set_linewidth(0.5)

    plt.tight_layout()
    plt.show()


def generate_random_data(days, shifts, min_value, max_value):
    return {(day, shift): round(random.uniform(min_value, max_value), 2) for day in range(1, days + 1) for shift in range(1, shifts + 1)}

demand_dict, u1, u2 = generate_random_data(28, 3, 0, 105), generate_random_data(28, 3, 0, 100), generate_random_data(28, 3, 0, 100)

plot_relative_undercover_dual(u1, u2, demand_dict, 28, 3)

原理说明

  • rotation_mode='anchor':指定文本围绕锚点(由ha和va定义的位置)旋转,而非文本自身中心,保证旋转后锚点位置不变
  • va='center' + ha='center':让文本的水平、垂直中点同时对齐柱状顶部中心,实现精准居中
  • 微小的y轴偏移(+0.005)是为了避免标签与柱状顶部完全重叠,可根据实际显示效果调整

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 15:00:11