import os import platform import tempfile from typing import List, Dict, Tuple, Optional import matplotlib.pyplot as plt import numpy as np from docx import Document from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.oxml.shared import OxmlElement from docx.shared import Inches, Pt, RGBColor from matplotlib import font_manager class DocxGenerator: def __init__(self, filename: str = "output.docx"): """ 初始化文档生成器 :param filename: 输出文件名 """ self.document = Document() self.filename = filename self.temp_files = [] # 用于存储临时文件路径 # 设置默认字体 self._set_default_font() # 设置matplotlib中文字体 self._set_matplotlib_font() def _set_default_font(self): """设置文档默认字体""" style = self.document.styles['Normal'] font = style.font font.name = '微软雅黑' font.size = Pt(10.5) def _set_matplotlib_font(self): """设置matplotlib中文字体""" # 根据操作系统选择字体 system = platform.system() if system == 'Windows': # Windows系统通常有微软雅黑 plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 设置字体为微软雅黑 elif system == 'Darwin': # Mac系统通常有PingFang SC plt.rcParams['font.sans-serif'] = ['PingFang SC'] # 设置字体为苹方 else: # Linux系统尝试使用文泉驿微米黑 plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei'] # 设置字体为文泉驿微米黑 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 如果上述字体不存在,尝试查找系统中可用的中文字体 try: # 查找系统中可用的中文字体 chinese_fonts = [f.name for f in font_manager.fontManager.ttflist if 'Hei' in f.name or 'hei' in f.name or 'YaHei' in f.name or 'yahei' in f.name or 'Song' in f.name or 'song' in f.name or 'Kai' in f.name or 'kai' in f.name or 'Fang' in f.name or 'fang' in f.name] if chinese_fonts: plt.rcParams['font.sans-serif'] = [chinese_fonts[0]] except: pass def add_title(self, text: str, level: int = 1): """ 添加标题 :param text: 标题文本 :param level: 标题级别 (1-3) """ if level == 1: self.document.add_heading(text, level=0) elif level == 2: self.document.add_heading(text, level=1) elif level == 3: self.document.add_heading(text, level=2) else: self.document.add_heading(text, level=2) def add_paragraph(self, text: str, bold: bool = False, italic: bool = False, underline: bool = False, color: Optional[Tuple[int, int, int]] = None, alignment: str = "left"): """ 添加段落文本 :param text: 文本内容 :param bold: 是否加粗 :param italic: 是否斜体 :param underline: 是否下划线 :param color: 文字颜色 (R, G, B) :param alignment: 对齐方式 ("left", "center", "right", "justify") """ paragraph = self.document.add_paragraph() # 设置对齐方式 if alignment == "center": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER elif alignment == "right": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT elif alignment == "justify": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY else: paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT run = paragraph.add_run(text) run.bold = bold run.italic = italic run.underline = underline if color: run.font.color.rgb = RGBColor(*color) def add_line_break(self): """添加换行""" self.document.add_paragraph() def add_page_break(self): """添加分页符""" self.document.add_page_break() def _save_plot_to_tempfile(self, plt_obj): """保存matplotlib图表到临时文件""" temp_file = tempfile.NamedTemporaryFile(suffix=".png", delete=False) self.temp_files.append(temp_file.name) plt_obj.savefig(temp_file.name, dpi=300, bbox_inches='tight') plt.close() return temp_file.name def add_line_chart(self, title: str, categories: List[str], series: Dict[str, List[float]], width: float = 6, height: float = 4): """ 添加折线图 :param title: 图表标题 :param categories: 类别列表 (x轴) :param series: 数据系列字典 {系列名: 数据列表} :param width: 图表宽度 (英寸) :param height: 图表高度 (英寸) """ plt.figure(figsize=(width, height)) for name, values in series.items(): plt.plot(categories, values, marker='o', label=name) plt.title(title, fontproperties=self._get_chinese_font()) plt.xlabel('分类', fontproperties=self._get_chinese_font()) plt.ylabel('数值', fontproperties=self._get_chinese_font()) plt.legend(prop=self._get_chinese_font()) plt.grid(True) # 保存图表并插入到文档 temp_file = self._save_plot_to_tempfile(plt) self._add_image_to_doc(temp_file, width, height, title) def add_pie_chart(self, title: str, data: Dict[str, float], width: float = 6, height: float = 4): """ 添加饼图 :param title: 图表标题 :param data: 数据字典 {标签: 值} :param width: 图表宽度 (英寸) :param height: 图表高度 (英寸) """ plt.figure(figsize=(width, height)) labels = list(data.keys()) sizes = list(data.values()) plt.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, textprops={'fontproperties': self._get_chinese_font()}) plt.axis('equal') # 保持圆形 plt.title(title, fontproperties=self._get_chinese_font()) # 保存图表并插入到文档 temp_file = self._save_plot_to_tempfile(plt) self._add_image_to_doc(temp_file, width, height, title) def add_rose_chart(self, title: str, data: Dict[str, float], width: float = 6, height: float = 4): """ 添加玫瑰图 (极坐标条形图) :param title: 图表标题 :param data: 数据字典 {标签: 值} :param width: 图表宽度 (英寸) :param height: 图表高度 (英寸) """ plt.figure(figsize=(width, height)) labels = list(data.keys()) values = list(data.values()) N = len(labels) theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False) radii = values width = 2 * np.pi / N * 0.8 ax = plt.subplot(111, polar=True) bars = ax.bar(theta, radii, width=width, bottom=0.0) # 设置标签 ax.set_xticks(theta) ax.set_xticklabels(labels, fontproperties=self._get_chinese_font()) ax.set_title(title, fontproperties=self._get_chinese_font()) # 设置颜色 for r, bar in zip(radii, bars): bar.set_facecolor(plt.cm.viridis(r / max(radii))) bar.set_alpha(0.8) # 保存图表并插入到文档 temp_file = self._save_plot_to_tempfile(plt) self._add_image_to_doc(temp_file, height, height, title) def _get_chinese_font(self): """获取中文字体属性""" from matplotlib.font_manager import FontProperties system = platform.system() if system == 'Windows': return FontProperties(fname='C:/Windows/Fonts/msyh.ttc', size=12) # 微软雅黑 elif system == 'Darwin': return FontProperties(fname='/System/Library/Fonts/PingFang.ttc', size=12) # 苹方 else: return FontProperties(fname='/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc', size=12) # 文泉驿微米黑 def _add_image_to_doc(self, image_path: str, width: float, height: float, title: str = None): """将图片添加到文档""" paragraph = self.document.add_paragraph() run = paragraph.add_run() run.add_picture(image_path, width=Inches(width), height=Inches(height)) # 添加图表标题 if title: self.add_paragraph(f"图: {title}", alignment="center") def add_table(self, data: List[List[str]], header: bool = True): """ 添加表格 :param data: 表格数据 (二维列表) :param header: 第一行是否为表头 """ table = self.document.add_table(rows=len(data), cols=len(data[0])) for i, row in enumerate(data): for j, cell in enumerate(row): table.cell(i, j).text = cell # 设置表头样式 if header and len(data) > 0: for j in range(len(data[0])): table.cell(0, j).paragraphs[0].runs[0].bold = True # 设置表头背景色 shading_elm = OxmlElement('w:shd') shading_elm.set(qn('w:fill'), 'D9D9D9') table.cell(0, j)._tc.get_or_add_tcPr().append(shading_elm) def generate(self): """ 生成并保存文档 :return: 生成的文件路径 """ # 保存文档 self.document.save(self.filename) # 清理临时文件 for temp_file in self.temp_files: try: os.unlink(temp_file) except: pass return self.filename # 使用示例 if __name__ == "__main__": # 创建文档生成器 doc = DocxGenerator("示例文档1.docx") # 添加标题 doc.add_title("年度销售报告", level=1) doc.add_line_break() # 添加段落 doc.add_paragraph("本报告展示公司2023年度的销售情况分析。", bold=True) doc.add_paragraph("报告包含以下内容:") doc.add_paragraph("1. 各季度销售趋势", color=(0, 0, 255)) doc.add_paragraph("2. 产品销售占比", color=(0, 0, 255)) doc.add_paragraph("3. 区域销售分布", color=(0, 0, 255)) doc.add_line_break() doc.add_paragraph(""" 1. 各季度销售趋势 2. 产品销售占比 3. 区域销售分布 """, color=(0, 0, 255)) doc.add_line_break() # 添加折线图 doc.add_title("季度销售趋势", level=2) categories = ["第一季度", "第二季度", "第三季度", "第四季度"] series = { "产品A": [120, 150, 180, 200], "产品B": [80, 90, 110, 130], "产品C": [50, 70, 90, 100] } doc.add_line_chart("季度销售趋势", categories, series) doc.add_line_break() # 添加饼图 doc.add_title("产品销售占比", level=2) pie_data = { "产品A": 45, "产品B": 30, "产品C": 25 } doc.add_pie_chart("产品销售占比", pie_data) doc.add_line_break() # 添加玫瑰图 doc.add_title("区域销售分布", level=2) rose_data = { "华东地区": 35, "华北地区": 25, "华南地区": 20, "西部地区": 15, "东北地区": 5 } doc.add_rose_chart("区域销售分布", rose_data) doc.add_line_break() # 添加表格 doc.add_title("详细销售数据", level=2) table_data = [ ["季度", "产品A", "产品B", "产品C", "总计"], ["第一季度", "120", "80", "50", "250"], ["第二季度", "150", "90", "70", "310"], ["第三季度", "180", "110", "90", "380"], ["第四季度", "200", "130", "100", "430"] ] doc.add_table(table_data) # 生成文档 output_file = doc.generate() print(f"文档已生成: {output_file}")