柱状图旋转数值标签未居中问题及解决需求
解决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
相关产品推荐
相关产品推荐

