from pptx import Presentation from pptx.util import Inches, Pt, Cm from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN from pptx.enum.text import MSO_ANCHOR # 导入正确的垂直对齐枚举 from pptx.enum.shapes import MSO_SHAPE from pptx.chart.data import ChartData from pptx.enum.chart import XL_CHART_TYPE,XL_LABEL_POSITION,XL_LEGEND_POSITION from pptx.table import Table from parse_html import parse_html_to_ppt from init import gcfg def apply_gradient_background(slide): """应用统一的渐变背景""" background = slide.background fill = background.fill fill.gradient() fill.gradient_stops[0].color.rgb = RGBColor(0, 32, 96) # 深蓝 fill.gradient_stops[1].color.rgb = RGBColor(0, 112, 192) # 蓝色 def create_content_slide(prs, title_text, subtitle_text=None, content_items=None): """创建带二级标题的内容页""" slide_layout = prs.slide_layouts[6] # 空白布局 slide = prs.slides.add_slide(slide_layout) apply_gradient_background(slide) # 添加主标题 left = Cm(1.5) top = Cm(1.5) width = Cm(30) height = Cm(2) title_box = slide.shapes.add_textbox(left, top, width, height) tf = title_box.text_frame tf.word_wrap = True tf.clear() p = tf.add_paragraph() p.text = title_text p.font.size = Pt(36) p.font.color.rgb = RGBColor(255, 255, 255) p.font.bold = True # 添加二级标题(如果有) if subtitle_text: left = Cm(1.5) top = Cm(3) width = Cm(30) height = Cm(1.5) subtitle_box = slide.shapes.add_textbox(left, top, width, height) tf = subtitle_box.text_frame tf.word_wrap = True tf.clear() p = tf.add_paragraph() p.text = subtitle_text p.font.size = Pt(24) p.font.color.rgb = RGBColor(200, 230, 255) # 浅蓝色 p.font.italic = True # 添加内容区域,多级列表 if content_items: # 添加内容文字 left_text = Cm(2) top_text = Cm(5.5 if subtitle_text else 4.5) width_text = Cm(36) height_text = Cm(8) text_box = slide.shapes.add_textbox(left_text, top_text, width_text, height_text) tf = text_box.text_frame # 启用自动换行 tf.word_wrap = True tf.clear() for item in content_items: p = tf.add_paragraph() p.text = item["text"] p.font.size = Pt(item.get("size", 18)) p.font.color.rgb = RGBColor(200, 230, 255) p.space_after = Pt(item.get("space_after", 8)) p.level = item.get("level", 0) if item.get("bold", False): p.font.bold = True if item.get("bullet", False): p.text = "• " + p.text return slide def create_table_slide(prs, title_text, subtitle_text, headers, data,content): """创建带表格的数据页""" slide = create_content_slide(prs, title_text, subtitle_text,content) # 计算表格位置和大小 left = Cm(2) top = Cm(8 if content else 7) #有内容则朝下一些 width = Cm(26.5) height = Cm(12 if len(data) >10 else 8) #表格数据比较多 # 创建表格 (行数=数据行数+1,列数=标题数) rows = len(data) + 1 cols = len(headers) table = slide.shapes.add_table(rows, cols, left, top, width, height).table # 设置表格样式 table.first_row = True # 强调第一行 table.horz_banding = True # 横向条纹 # 设置表头 for col_idx, header in enumerate(headers): cell = table.cell(0, col_idx) cell.text = header cell.fill.solid() cell.fill.fore_color.rgb = RGBColor(0, 64, 128) # 深蓝色表头 cell.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255) cell.text_frame.paragraphs[0].font.bold = True cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER # 填充表格数据 for row_idx, row_data in enumerate(data, start=1): for col_idx, cell_data in enumerate(row_data): cell = table.cell(row_idx, col_idx) cell.text = str(cell_data) cell.fill.solid() cell.fill.fore_color.rgb = RGBColor(255, 255, 255) cell.fill.fore_color.alpha = 0.2 # 半透明白色 cell.text_frame.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0) # 黑色文字 cell.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER # #边框线(赞不支持) # for border_side in ["top", "bottom", "left", "right"]: # border = getattr(cell, f"{border_side}_border") # border.fill.background() # 清除任何填充色,确保只应用线条样式 # border.color.rgb = RGBColor(0, 64, 128) # border.size = Cm(0.1) # 设置表格边框 for cell in table.iter_cells(): cell.margin_left = Cm(0.1) cell.margin_right = Cm(0.1) cell.margin_top = Cm(0.05) cell.margin_bottom = Cm(0.05) cell.vertical_anchor = MSO_ANCHOR.MIDDLE return slide def create_chart_slide(prs, title_text, subtitle_text, chart_type, data): """创建图表的数据页""" slide = create_content_slide(prs, title_text, subtitle_text) #半透明白色背景 left_content = Cm(2) top_content = Cm(7 if subtitle_text else 6) # 根据是否有二级标题调整位置 width_content = Cm(28.5) height_content = Cm(15 if len(data) >10 else 12 ) content_box = slide.shapes.add_shape( MSO_SHAPE.RECTANGLE, left_content, top_content, width_content, height_content ) content_box.fill.solid() content_box.fill.fore_color.rgb = RGBColor(255, 255, 255) content_box.fill.fore_color.alpha = 0.2 # 20%透明度 content_box.line.color.rgb = RGBColor(200, 230, 255) # 和前端颜色一致 colors = [ "#FF6384", # 对应 rgba(255,99,132,1) "#36A2EB", # 对应 rgba(54, 162, 235, 1) "#FFCE56", # 对应 rgba(255, 206, 86, 1) "#4BC0C0", # 对应 rgba(75, 192, 192, 1) "#9966FF", # 对应 rgba(153, 102, 255, 1) "#FF9F40", # 对应 rgba(255, 159, 64, 1) "#FF6347", # 新增:对应 rgba(255, 99, 71, 1),番茄红 "#90EE90", # 新增:对应 rgba(144, 238, 144, 1),淡绿 "#ADD8E6", # 新增:对应 rgba(173, 216, 230, 1),浅天蓝 "#FFC0CB" # 新增:对应 rgba(255, 192, 203, 1),浅粉 ] # 添加图表数据 if data: chart_data = ChartData() chart_data.categories = data["categories"] del data["categories"] for lable, series in data.items(): chart_data.add_series(lable, series) # 添加图表 left_chart = Cm(3) top_chart = Cm(8) width_chart = Cm(25) height_chart = Cm(12 if len(data) >10 else 10 ) if chart_type=="[chart][bar]": xl_chart_type=XL_CHART_TYPE.COLUMN_CLUSTERED elif chart_type=="[chart][line]": xl_chart_type=XL_CHART_TYPE.LINE elif chart_type=="[chart][bar_line]": xl_chart_type=XL_CHART_TYPE.BAR_CLUSTERED elif chart_type=="[chart][area]": xl_chart_type=XL_CHART_TYPE.AREA else: xl_chart_type=XL_CHART_TYPE.BAR_STACKED chart = slide.shapes.add_chart( xl_chart_type,left_chart, top_chart, width_chart, height_chart, chart_data ) # 设置数据标签 plot = chart.chart.plots[0] plot.has_data_labels = True data_labels = plot.data_labels data_labels.show_value = True # 显示数值 data_labels.position = XL_LABEL_POSITION.OUTSIDE_END # 标签位置 # 设置数据标签的字体大小 data_labels.font.size = Pt(6) # 假设你想要设置字体大小为12磅 # 设置图例 chart.chart.has_legend = True # 确保图例可见 chart.chart.legend.position = XL_LEGEND_POSITION.BOTTOM # 设置图例位置为底部 chart.chart.legend.include_in_layout = False # 不让图例覆盖图表 # 设置图表颜色 if len(chart.chart.series) ==1: #只有一个series for idx, point in enumerate(chart.chart.series[0].points): if idx < len(colors): # 确保颜色数量足够 fill = point.format.fill fill.solid() fill.fore_color.alpha = 0.2 # 20%透明度 fill.fore_color.rgb = RGBColor.from_string(colors[idx][1:]) else: #设置图表颜色 - 为每个系列分配一种颜色 for idx, serie in enumerate(chart.chart.series): if idx < len(colors): # 确保颜色数量足够 fill = serie.format.fill fill.solid() fill.fore_color.rgb = RGBColor.from_string(colors[idx][1:]) # 移除前缀'#' # # 设置图表颜色以匹配主题 # chart.chart.plots[0].series[0].format.fill.solid() # chart.chart.plots[0].series[0].format.fill.fore_color.rgb = RGBColor(100, 180, 255) # chart.chart.plots[0].series[1].format.fill.solid() # chart.chart.plots[0].series[1].format.fill.fore_color.rgb = RGBColor(200, 230, 255) return slide def create_image_layout_slide(prs, title_text, subtitle_text, images,content_items=None): """创建图片布局页面""" slide = create_content_slide(prs, title_text, subtitle_text) # 清除默认内容区域 for shape in slide.shapes: if shape.shape_type == MSO_SHAPE.ROUNDED_RECTANGLE: sp = shape._element sp.getparent().remove(sp) # 根据图片数量决定布局 if len(images) == 1: # 单张大图布局 left = Cm(3) top = Cm(5 if subtitle_text else 4)+Cm(0.5) width = Cm(26) height = Cm(15) pic = slide.shapes.add_picture(images[0]["path"], left, top, width, height) # 添加图片说明 if "caption" in images[0]: left_cap = Cm(3) top_cap = Cm(19 if subtitle_text else 20) width_cap = Cm(25) height_cap = Cm(1) cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) tf = cap.text_frame p = tf.add_paragraph() if images[0]["caption"]: p.text = images[0]["caption"] else: p.text = "主图" p.font.size = Pt(14) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.CENTER # 在图片的右侧添加内容区域,可以多级列表 if content_items: # 添加内容文字 left_text = Cm(30) top_text = Cm(5.5 if subtitle_text else 4.5) width_text = Cm(5) height_text = Cm(18) text_box = slide.shapes.add_textbox(left_text, top_text, width_text, height_text) tf = text_box.text_frame # 启用自动换行 tf.word_wrap = True tf.clear() for item in content_items: p = tf.add_paragraph() p.text = item["text"] p.font.size = Pt(item.get("size", 18)) p.font.color.rgb = RGBColor(200, 230, 255) p.space_after = Pt(item.get("space_after", 8)) p.level = item.get("level", 0) if item.get("bold", False): p.font.bold = True if item.get("bullet", False): p.text = "• " + p.text elif len(images) == 2: # 两张图片并排布局,大小一致 # 第一张图片 left1 = Cm(2) top1 = Cm(5 if subtitle_text else 4)+Cm(0.5) width1 = Cm(13) height1 = Cm(15) pic1 = slide.shapes.add_picture(images[0]["path"], left1, top1, width1, height1) # 第二张图片 left2 = Cm(16) top2 = Cm(5 if subtitle_text else 4)+Cm(0.5) width2 = Cm(13) height2 = Cm(15) pic2 = slide.shapes.add_picture(images[1]["path"], left2, top2, width2, height2) # 添加图片说明 for i, img in enumerate(images): if "caption" in img: left_cap = Cm(2 if i == 0 else 19) top_cap = Cm(17 if subtitle_text else 16) width_cap = Cm(13) height_cap = Cm(1) cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) tf = cap.text_frame p = tf.add_paragraph() if img["caption"]: p.text = img["caption"] else: p.text = f"图片{i+1}" p.font.size = Pt(12) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.CENTER # 在图片的右侧添加内容区域,可以多级列表 if content_items: # 添加内容文字 left_text = Cm(30) top_text = Cm(5.5 if subtitle_text else 4.5) width_text = Cm(5) height_text = Cm(18) text_box = slide.shapes.add_textbox(left_text, top_text, width_text, height_text) tf = text_box.text_frame # 启用自动换行 tf.word_wrap = True tf.clear() for item in content_items: p = tf.add_paragraph() p.text = item["text"] p.font.size = Pt(item.get("size", 18)) p.font.color.rgb = RGBColor(200, 230, 255) p.space_after = Pt(item.get("space_after", 8)) p.level = item.get("level", 0) if item.get("bold", False): p.font.bold = True if item.get("bullet", False): p.text = "• " + p.text elif len(images) >= 3: # 三张以上图片网格布局 # 第一行,三张 if len(images)==3: img_size = Cm(12) else: img_size = Cm(8) gap = Cm(1) for i in range(min(3, len(images))): left = Cm(3) + (img_size + gap) * i top = Cm(5 if subtitle_text else 4)+Cm(0.5) pic = slide.shapes.add_picture(images[i]["path"], left, top, img_size, img_size) if "caption" in images[i]: left_cap = left top_cap = top + img_size + Cm(0.5) width_cap = img_size height_cap = Cm(1) cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) tf = cap.text_frame p = tf.add_paragraph() if images[i]["caption"]: p.text = images[i]["caption"] else: p.text = f"图片{i+1}" p.font.size = Pt(10) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.CENTER # 第二行(如果有4张以上图片) img_size = Cm(4) gap = Cm(1) for i in range(3, len(images)): left = Cm(3) + (img_size + gap) * (i - 3) top = Cm(16 if subtitle_text else 15) pic = slide.shapes.add_picture(images[i]["path"], left, top, img_size, img_size) if "caption" in images[i] and images[i]["caption"]: left_cap = left top_cap = top + img_size + Cm(0.5) width_cap = img_size height_cap = Cm(1) cap = slide.shapes.add_textbox(left_cap, top_cap, width_cap, height_cap) tf = cap.text_frame p = tf.add_paragraph() p.text = images[i]["caption"] p.font.size = Pt(10) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.CENTER return slide # ===== 封面幻灯片 ===== def create_main_slide(prs, title_text, subject_text,content=None): # ===== 封面幻灯片 ===== slide_layout = prs.slide_layouts[6] # 空白布局 slide = prs.slides.add_slide(slide_layout) apply_gradient_background(slide) #添加宽屏主题 left = Cm(2) top = Cm(8) width = Cm(36) height = Cm(6) subtitle_box = slide.shapes.add_textbox(left, top, width, height) tf = subtitle_box.text_frame tf.word_wrap = True p = tf.add_paragraph() p.text = title_text p.font.size = Pt(66) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.LEFT # 添加副标题 if subject_text: left = Cm(1.5) top = Cm(1.5) width = Cm(36) height = Cm(4) title_box = slide.shapes.add_textbox(left, top, width, height) tf = title_box.text_frame tf.word_wrap = True p = tf.add_paragraph() p.text = subject_text p.font.size = Pt(36) p.font.color.rgb = RGBColor(255, 255, 255) p.font.bold = True p.alignment = PP_ALIGN.LEFT # 添加其它标题 if content: i=0 for line in content: left = Cm(12) top = Cm(12+i) width = Cm(16) height = Cm(2) subtitle_box = slide.shapes.add_textbox(left, top, width, height) tf = subtitle_box.text_frame p = tf.add_paragraph() p.text = line["text"] p.font.size = Pt(28) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.CENTER i+=3 #end # ===== 结束幻灯片 ===== def create_end_slide(prs,title,subtitle): slide = create_content_slide( prs, title, subtitle ) img_width = Cm(8) img_height = Cm(4) print(prs.slide_width,prs.slide_width/Cm(1),prs.slide_width/Cm(1)/2-3) left = Cm(prs.slide_width/Cm(1)/2-3) #屏幕中央 top = Cm(8) #添加logo if gcfg["fs"]["logo"]!="": pic = slide.shapes.add_picture(f'{gcfg["fs"]["path"]}/img/{gcfg["fs"]["logo"]}', left, top, img_width, img_height) else: pic = slide.shapes.add_picture(f'ui/images/logo2.jpg', left, top, img_width, img_height) # 在内容区域添加slogan和网址 left = Cm(12) top = Cm(12) width = Cm(16) height = Cm(3) contact_box = slide.shapes.add_textbox(left, top, width, height) tf = contact_box.text_frame p = tf.add_paragraph() p.text = gcfg["fs"]["slogan"] p.font.size = Pt(28) p.font.color.rgb = RGBColor(255, 255, 255) p.font.bold = True p.alignment = PP_ALIGN.CENTER p.space_after = Pt(16) p = tf.add_paragraph() p.text = gcfg["fs"]["url"] p.font.size = Pt(18) p.font.color.rgb = RGBColor(200, 230, 255) p.alignment = PP_ALIGN.CENTER p.space_before = Pt(10) #入口函数,根据html创建ppt def create_unified_ppt(html,output_filename): # 创建一个16:9宽屏演示文稿对象 prs = Presentation() #prs.slide_width = Inches(13.333) # 16:9的宽度 #prs.slide_height = Inches(7.5) # 16:9的高度 prs.slide_width = Inches(16) # 16:9的宽度 prs.slide_height = Inches(9) # 16:9的高度 ppt = parse_html_to_ppt(html) for slide in ppt: if slide["type"]=="main": create_main_slide(prs,slide["title"],slide["subtitle"],slide["content"]) elif slide["type"]=="text": create_content_slide( prs,slide["title"],slide["subtitle"],slide["content"] ) elif slide["type"]=="image": create_image_layout_slide( prs,slide["title"],slide["subtitle"],slide["images"],slide["content"] ) elif slide["type"]=="chart": create_chart_slide( prs,slide["title"],slide["subtitle"],slide["chart_type"],slide["data"] ) elif slide["type"]=="table": create_table_slide( prs,slide["title"],slide["subtitle"],slide["header"],slide["data"],slide["content"] ) else: pass create_end_slide(prs,"感谢聆听","欢迎指正") # 保存演示文稿 prs.save(output_filename) if __name__ == "__main__": html_string ="""

K3GPT数据分析智能体

三国工资表.xls

问题

按如下分析三国详情:1不同集团的人数,2不同集团不同级别的人数,3不同集团的平均服务年龄,4不同集团工种帝王人数

数据分析

集团分布

[chart][bar]

{\"categories\":[\"魏\",\"吴\",\"无\",\"蜀\"],\"count\":[3,3,3,2]}

集团->职级的分布

[chart][bar]

{\"L4\":[10,9,7,1],\"L3\":[8,0,4,1],\"L5\":[6,9,6,3],\"L6\":[4,5,3,2],\"L7\":[3,4,3,2],\"L8\":[2,2,2,0],\"L9\":[1,1,1,0],\"L2\":[0,0,1,0],\"categories\":[\"吴\",\"魏\",\"蜀\",\"无\"]}

不同集团对应的司龄(年)的常见六指标分析

[chart][bar]

{\"categories\":[\"蜀\",\"魏\",\"无\",\"吴\"],\"总和\":[397,586,179,560],\"平均值\":[14.703703703703704,19.533333333333335,19.88888888888889,16.470588235294116],\"中位数\":[12,19,20,15],\"最小值\":[5,10,8,5],\"最大值\":[40,35,30,40],\"个数\":[27,30,9,34]}

表格1

序号姓名集团工种职级工资(万)司龄(年)
1曹操帝王L91030
2刘备帝王L99.825
3孙权帝王L99.540
53孙策帝王L8810
54孙坚帝王L7715
55刘禅帝王L7640
56曹丕帝王L88.515
57曹叡帝王L7710
97袁绍帝王L7620
98袁术帝王L6515
100刘表帝王L64.525

小结

根据分析结果,三国工资表的详细情况如下:

  1. 集团人数分布 吴国:34人 魏国:30人 蜀国:27人 无集团:9人
  2. 集团-职级人数分布 吴国主要职级为L4(10人)、L3(8人)、L5(6人) 魏国最高职级达L9(1人),L5(9人)占比较大 蜀国职级分布较均衡,L4(7人)、L5(6人)为主
  3. 平均服务年龄 魏国平均19.53年(最高) 吴国16.47年 蜀国14.70年(最低) 无集团19.89年(可能包含临时人员)
  4. 帝王工种分布 吴国、魏国、无集团各有3名帝王,蜀国2名 帝王多集中在高级职级(L6-L9)

分析点评:  

", """ # 使用前请替换示例图片路径为实际图片路径 create_unified_ppt(html_string,"test.pptx") print("带生成完成!")