from typing import Sequence
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
class NormalBarChart(object):
@staticmethod
def draw_bar(data_df: pd.Series, title: str, seq: list | None = None) -> None:
value_counts = data_df.value_counts()
if not seq:
seq = value_counts.index
bars = plt.bar(seq, [value_counts[j] for j in seq], color='skyblue')
for bar in bars:
yval = bar.get_height()
plt.text(bar.get_x() + bar.get_width() / 2, yval, yval, ha='center', va='bottom')
plt.xlabel(title)
plt.ylabel('频率')
class StackBarChart(object):
def __init__(
self,
data_group: dict[str: Sequence],
stack_label: Sequence[str | int],
bar_width: float = 0.8,
height_adjust: float = 0.0
):
# dict应该形如: {'Group 1': [10, 20, 30], 'Group 2': [40, 50, 60]}
self.xlabel: list = list(data_group.keys())
self.stack_label: Sequence = stack_label
self.stack_data = self.__get_stack_data(data_group)
self.bar_width = bar_width
self.height_adjust = height_adjust
self.colors = plt.cm.tab10.colors
def __get_stack_data(self, data_group: pd.DataFrame | dict):
transposed_data = [list(group) for group in zip(*data_group.values())]
return {k: v for k, v in zip(self.stack_label, transposed_data)}
def __draw(self, bottom: Sequence) -> None:
for values, label, color in zip(self.stack_data.values(), self.stack_label, self.colors):
bars = plt.bar(
self.xlabel, values, width=self.bar_width, label=label,
bottom=bottom, color=color
)
for bar, value, bot in zip(bars, values, bottom):
if value == 0:
continue
plt.text(
bar.get_x() + bar.get_width() / 2,
bot + bar.get_height() / 2 + self.height_adjust,
value, ha='center', va='bottom'
)
bottom += values # 更新底部高度
def draw(self, title: str, colors: Sequence = None) -> None:
bottom = np.zeros(len(self.xlabel)) # 初始化底部高度为0
if colors is not None:
if len(colors) != len(self.stack_label):
raise ValueError("The length of colors should be equal to the length of stack_label")
self.colors = colors
self.__draw(bottom)
plt.xlabel(title)
plt.ylabel("频数")
def show_legend(self, loc: str = "right"):
custom_handles = [plt.Rectangle((0, 0), 1, 1, color=color) for color in self.colors]
plt.legend(custom_handles, self.stack_label, loc=loc)
if __name__ == "__main__":
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号无法正常显示的问题
data_group = {
'Group 1': [10, 20, 30, 10],
'Group 2': [40, 50, 60, 10],
'Group 3': [70, 80, 90, 10]
}
# 创建实例并绘制图表
stack_label = ["同意", "不同意", "中立", "未表态"]
drawer = StackBarChart(data_group, stack_label, height_adjust=-4)
drawer.draw("测试")
drawer.show_legend("upper left")
plt.show()
运行后的效果图: