
简介本资源是一份面向Python初学者与数据可视化实践者的家谱图生成技术指南聚焦于从Excel结构化数据出发利用pandas读取家族成员信息、graphviz构建苏式五代世系图的完整实现路径。文档以清晰步骤讲解库安装、数据预处理、节点关系建模、兄弟节点横向对齐含subgraph与ranksame技巧、PDF渲染等核心环节并附带可直接运行的代码及关键注释说明。资源为1个15KB的docx文件内容涵盖方法原理、代码实现、Excel格式规范姓名/父亲/世代三列、常见适配建议及graphviz进阶配置提示便于读者快速复现并按实际家谱结构调整。目前已有122人学习下载适合需将家族数据转化为专业级谱系图的技术爱好者、家谱数字化项目参与者或Python数据处理教学辅助者。1. 用 Python Graphviz 把 Excel 里的五代家谱数据自动画成苏式世系图不是画树状图而是严格按“苏式体例”排版父在上、子居下、同辈横排、长幼有序、配偶单列、出继/入继标注清晰——这解决的是家谱数字化中最难落地的一环结构化数据到规范谱图的精准映射很多做家族文化整理的人卡在同一个环节Excel 表格里存着几百行亲属关系姓名、性别、出生年、父亲、母亲、配偶、是否出继、是否入继、备注但导出 PDF 或打印时要么靠手动拖拽 Visio 拼接耗时易错要么用通用树图工具如 networkx matplotlib生成的图辈分错位、配偶挤在节点里、继嗣关系无标识完全不符合《苏氏家谱凡例》中“世系分明、支派不淆、继嗣必书、配氏单列”的排版要求。本方案不依赖任何 GUI 工具或在线服务全程用pandas清洗 Excel 数据、用graphviz的 DOT 语言精确控制节点位置与连接逻辑、用自定义布局规则实现“苏式五代”特有的层级压缩——祖父辈居顶、父辈居中、己辈居下、子辈再下、孙辈最底且每代内部严格按长幼顺序横向排列配偶节点右悬于夫/妻节点旁出继者加出继X房标注入继者加入继X房标注。适合有 Excel 家谱数据但无编程经验的文史工作者也适合需要批量生成多支系谱图的修谱机构。2. 解析 Excel 家谱数据结构并构建苏式五代拓扑关系从宽表到父子-配偶-继嗣三元组2.1 苏式家谱对原始 Excel 数据的字段约束与清洗逻辑苏式体例要求世系图必须体现“以父系为主线、配偶为附属、继嗣为变通”的结构因此 Excel 原始数据不能是扁平宽表如 A1:姓名, B1:父名, C1:母名, D1:配偶名, E1:性别…而应满足以下最小字段集字段名必填说明示例name✓本人姓名唯一标识张明远father✓父亲姓名空值表示始祖张守仁gender✓性别仅限 男/女男spouse✗配偶姓名可为空女性配偶字段不填李秀英is_adopted_in✗是否入继填入继房名如“张守义房”张守义房is_adopted_out✗是否出继填出继房名如“张守礼房”张守礼房birth_year✗出生年份用于排序非必需1942提示若原始 Excel 含重复姓名如多名“张建国”必须添加唯一 ID 列如id并用id作为主键关联name仅作显示文本。pandas默认以name为索引会因重名报错这是新手最常踩的第一个坑。2.2 用 pandas 构建父子边、配偶边、继嗣边三类关系图谱我们不直接用networkx建图而是先用pandas生成三张关系表parent_edges父→子、spouse_edges夫↔妻、adoption_edges继入/继出标注。这样能显式控制每条边的语义和渲染样式。import pandas as pd # 读取 Excel假设工作表名为 family df pd.read_excel(su_shi_family.xlsx, sheet_namefamily, dtypestr) # 步骤1补全空值避免后续 merge 失败 df df.fillna() # 步骤2构建父子边表 —— 每个非始祖成员生成一条 father → name 边 parent_edges df[df[father] ! ].copy() parent_edges parent_edges[[father, name]].rename(columns{father: src, name: dst}) # 步骤3构建配偶边表 —— 仅当 spouse 非空且 gender 为 男 时生成避免女性配偶重复 spouse_edges df[(df[spouse] ! ) (df[gender] 男)].copy() spouse_edges spouse_edges[[name, spouse]].rename(columns{name: src, spouse: dst}) # 步骤4构建继嗣标注表 —— 合并 is_adopted_in 和 is_adopted_out 到同一列 df[adoption_note] df.loc[df[is_adopted_in] ! , adoption_note] 入继 df[is_adopted_in] df.loc[df[is_adopted_out] ! , adoption_note] 出继 df[is_adopted_out] # 步骤5提取五代核心成员 —— 以某位“己身”如张明远为基准向上推两代、向下推两代 target 张明远 ancestors set() descendants set() # 向上找祖父、曾祖最多两代 current target for _ in range(2): father_row df[df[name] current] if not father_row.empty and father_row.iloc[0][father]: ancestors.add(father_row.iloc[0][father]) current father_row.iloc[0][father] else: break # 向下找子女、孙子女最多两代 def get_descendants(name, depth0): if depth 2: return children df[df[father] name][name].tolist() for child in children: descendants.add(child) get_descendants(child, depth 1) get_descendants(target, 0) # 合并五代成员祖先 自身 子孙 five_gen_names list(ancestors) [target] list(descendants) core_df df[df[name].isin(five_gen_names)].copy()这段代码的核心价值不在语法而在逻辑它把 Excel 中隐含的“血缘链”显式拆解为可验证的边关系并通过get_descendants递归确保向下两代不漏人比如张明远有子张立新、张立业张立新又有子张承志则张承志必须包含在五代内。core_df即为最终绘图的数据源后续所有节点属性如排序序号、辈分层级都基于此子集计算。2.3 计算每个成员的苏式辈分层级与同辈排序序号苏式图谱的视觉秩序依赖两个坐标垂直方向的辈分层rank和水平方向的长幼序order。graphviz的ranksame只能保证同层但无法自动排序必须由pandas预先算出每个节点的rank和order值。# 步骤1确定各成员辈分层级rank # 规则始祖 rank0其子 rank1孙 rank2依此类推 rank_map {} # 先找始祖father 为空者 founders core_df[core_df[father] ] for _, row in founders.iterrows(): rank_map[row[name]] 0 # BFS 方式逐层推算后代 rank from collections import deque queue deque(founders[name].tolist()) while queue: person queue.popleft() children core_df[core_df[father] person][name].tolist() for child in children: if child not in rank_map: rank_map[child] rank_map[person] 1 queue.append(child) core_df[rank] core_df[name].map(rank_map).fillna(0).astype(int) # 步骤2计算同辈内长幼序order—— 按 birth_year 升序无年份则按 Excel 行序 core_df[birth_year] pd.to_numeric(core_df[birth_year], errorscoerce) core_df core_df.sort_values([rank, birth_year, name]).reset_index(dropTrue) core_df[order] core_df.groupby(rank).cumcount() 1 # 步骤3为每个节点生成唯一 graphviz ID防中文乱码、空格问题 core_df[node_id] core_df[name].str.replace(r[^\w], _, regexTrue) _ core_df[rank].astype(str)关键参数说明rank整数0 表示最高辈曾祖4 表示最低辈孙五代即rank∈ {0,1,2,3,4}order每rank组内从 1 开始编号决定节点在该层的水平位置node_id将中文姓名转为合法 DOT 标识符如“张明远”→Zhang_Mingyuan_2避免graphviz解析失败。3. 用 Graphviz DOT 语言生成苏式布局rankdirTB ranksame edge constraints 控制五代纵向压缩与同辈横排3.1 构建符合苏式体例的 DOT 文件骨架subgraph 分层 node 属性定制Graphviz 默认的dot引擎会自动调整布局但苏式图谱要求严格纵向分层、同辈绝对横排、配偶右悬、继嗣标注紧贴节点。必须用subgraph显式声明每层并设置ranksame同时禁用边的自动排序constraintfalse以避免配偶边干扰父子层级。from graphviz import Digraph dot Digraph(commentSu-style Five-Generation Genealogy, enginedot, formatpng) dot.attr(rankdirTB, splinesortho, nodesep25, ranksep60, fontsize12, fontnameSimSun) # 步骤1为每个 rank 创建 subgraph并设置 ranksame ranks sorted(core_df[rank].unique()) for r in ranks: with dot.subgraph() as s: s.attr(ranksame) # 添加该层所有节点 layer_nodes core_df[core_df[rank] r] for _, row in layer_nodes.iterrows(): # 节点标签姓名 继嗣标注如有 label row[name] if row[adoption_note]: label row[adoption_note] # 节点形状男性用 box女性用 ellipse shape box if row[gender] 男 else ellipse # 节点样式浅灰填充、黑边、居中对齐 dot.node( row[node_id], labellabel, shapeshape, stylefilled, fillcolor#f0f0f0, color#333333, fontnameSimSun, fontsize12, width1.8, height0.6, fixedsizetrue ) # 步骤2添加父子边垂直向下带 arrowheadvee for _, edge in parent_edges.iterrows(): src_id core_df[core_df[name] edge[src]][node_id].iloc[0] if not core_df[core_df[name] edge[src]].empty else None dst_id core_df[core_df[name] edge[dst]][node_id].iloc[0] if not core_df[core_df[name] edge[dst]].empty else None if src_id and dst_id: dot.edge(src_id, dst_id, arrowheadvee, arrowsize0.8, penwidth1.2, constrainttrue) # 步骤3添加配偶边水平右向无箭头加 spouse 标签 for _, edge in spouse_edges.iterrows(): src_id core_df[core_df[name] edge[src]][node_id].iloc[0] dst_id core_df[core_df[name] edge[dst]][node_id].iloc[0] if src_id and dst_id: dot.edge( src_id, dst_id, arrowheadnone, penwidth1.0, label配, fontsize10, fontcolor#555555, constraintfalse # 关键禁用约束让配偶边不拉高父辈层 )注意constraintfalse是配偶边不破坏父子垂直流的关键。若设为trueGraphviz 会强制配偶边两端节点在同一 rank导致父辈被拉低、子辈被抬高彻底打乱五代纵向压缩结构。3.2 强制同辈节点按 order 值严格左→右排列用 invisible edges 锚定顺序仅靠ranksame不能保证节点按order排列——Graphviz 可能因边权重随机排序。必须添加不可见的invis边形成“虚拟链”强制order1→order2→order3的线性顺序。# 为每个 rank 层内节点添加 invisible 边锚定水平顺序 for r in ranks: layer_nodes core_df[core_df[rank] r].sort_values(order) ids layer_nodes[node_id].tolist() for i in range(len(ids) - 1): dot.edge(ids[i], ids[i1], styleinvis, constrainttrue) # 可选为曾祖层rank0添加标题 if 0 in ranks: founder_ids core_df[core_df[rank] 0][node_id].tolist() if founder_ids: dot.node(title_node, label曾祖辈, shapeplaintext, fontsize14, fontweightbold) dot.edge(title_node, founder_ids[0], styleinvis)这段代码生成的 DOT 结构等价于手写如下片段subgraph cluster_0 { ranksame; title_node [label曾祖辈 shapeplaintext fontsize14 fontweightbold]; Zhang_Shouren_0 [label张守仁 shapebox ...]; title_node - Zhang_Shouren_0 [styleinvis]; } subgraph cluster_1 { ranksame; Zhang_Mingyuan_1 [label张明远 shapebox ...]; Li_Xiuying_1 [label李秀英配 shapeellipse ...]; Zhang_Mingyuan_1 - Li_Xiuying_1 [arrowheadnone label配 constraintfalse]; Zhang_Mingyuan_1 - Zhang_Lixin_2 [arrowheadvee]; }3.3 生成并渲染 PNG 图像处理中文字体与路径权限问题Mac / Windows 用户常因字体缺失导致中文显示为方框。graphviz默认不加载系统中文字体需显式指定fontname并确保字体文件存在。# 设置中文字体路径macOS 示例Windows 请替换为 simsun.ttc 路径 import platform if platform.system() Darwin: # macOS font_path /System/Library/Fonts/PingFang.ttc elif platform.system() Windows: font_path C:/Windows/Fonts/simsun.ttc else: # Linux font_path /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf # 将字体路径注入 DOT 属性 dot.attr(fontnamePingFang SC if platform.system() Darwin else SimSun) # 渲染图像 try: dot.render(filenamesu_shi_genealogy, directory./output, cleanupTrue, formatpng, viewFalse) print(✅ 苏式五代家谱图已生成./output/su_shi_genealogy.png) except Exception as e: print(f❌ 渲染失败{e}) print(请检查1. Graphviz 是否已安装2. 字体路径是否正确3. output 目录是否有写入权限)常见失败原因及修复ExecutableNotFound未安装 Graphviz 二进制。Mac 用brew install graphvizWindows 从官网下载安装包并添加到 PATH中文乱码确认fontname与系统字体名一致macOS 用PingFang SCWindows 用SimSunLinux 用WenQuanYi Zen Hei权限错误directory./output会创建该文件夹若当前目录只读改用绝对路径如/tmp/output。4. 调整苏式图谱的视觉精度配偶节点右悬距离、继嗣标注字号、辈分层间距的三个必调参数4.1 控制配偶节点与主体节点的水平偏移量用 xlabel pos 实现像素级定位默认的edge连接会让配偶节点紧贴主体右侧但苏式谱图要求配偶节点略下沉、右悬 30–50px且不参与同辈排序。graphviz不支持绝对坐标但可用xlabel在边上添加标注并用pos属性微调。# 替换原 spouse_edges 渲染逻辑改为主体节点 右侧独立配偶节点 无边连接线 for _, edge in spouse_edges.iterrows(): src_row core_df[core_df[name] edge[src]].iloc[0] dst_name edge[dst] # 生成配偶节点 ID加 _spouse 后缀 spouse_id src_row[node_id] _spouse # 渲染配偶节点椭圆、浅蓝填充、小字号 dot.node( spouse_id, labeldst_name, shapeellipse, stylefilled, fillcolor#e6f7ff, color#1890ff, fontnameSimSun, fontsize10, width1.2, height0.5, fixedsizetrue ) # 添加不可见边但用 pos 指定配偶节点相对位置x 偏移 60pxy 偏移 10px # 注意pos 是 x,y! 格式单位为磅pt1pt ≈ 1.33px dot.edge( src_row[node_id], spouse_id, styleinvis, posf{src_row[node_id]}:{src_row[node_id]}n -- {spouse_id}:{spouse_id}n [pos\e,60,10!\] )pose,60,10!含义从主体节点东侧e出发向右偏移 60pt≈80px向下偏移 10pt≈13px!表示绝对定位。此参数可精确控制配偶节点悬浮位置避免与子节点重叠。4.2 继嗣标注字号与颜色分离用 HTML-like label 实现多色文本adoption_note若直接拼在label中会与姓名同字号同色。苏式规范要求继嗣标注用小号灰色字如 9pt、#888姓名用 12pt 黑色。graphviz支持 HTML 标签可嵌套font。# 修改节点 label 构建逻辑 for _, row in core_df.iterrows(): base_label ffont point-size12{row[name]}/font if row[adoption_note]: note_label ffont point-size9 color#888{row[adoption_note]}/font full_label f{base_label}br/{note_label} else: full_label base_label dot.node( row[node_id], labelfull_label, shapebox if row[gender] 男 else ellipse, stylefilled, fillcolor#f0f0f0, color#333333, fontnameSimSun, fontsize12, # 此处仅影响非 HTML labelHTML 内字号以 font 为准 width1.8, height0.8, fixedsizetrue )br/换行确保姓名与标注分两行视觉上更清晰。point-size单位是磅pt9pt 约等于 12px符合印刷谱图小字规范。4.3 五代层间垂直间距微调ranksep 与 nodesep 的协同作用表ranksep控制层与层之间的最小距离nodesep控制同层节点间的最小距离。苏式图谱要求“代际分明、同辈紧凑”典型值如下参数推荐值效果调整场景ranksep60曾祖→祖父层间距 60pt≈80px避免上下代文字重叠若曾祖名过长调至70nodesep25同辈兄弟间最小水平距 25pt≈33px保证姓名不挤若某代人多如祖父有 5 子调至30fontsize全局12节点内文字基础字号打印 A3 图时可升至14提示修改后务必重新render()ranksep变化会触发 Graphviz 全局重排可能影响配偶节点位置需同步检查pos偏移值是否仍合适。5. 验证苏式五代图谱的合规性用三步法检查辈分、长幼、继嗣三大硬性指标5.1 辈分层级验证导出节点 rank 表并人工比对 Excel 原始关系链生成图后不能只看图像是否“像”必须验证rank值是否严格对应血缘深度。导出core_df[[name, father, rank]]到 CSV逐行核对# 导出辈分验证表 rank_check core_df[[name, father, rank]].sort_values(rank) rank_check.to_csv(./output/rank_verification.csv, indexFalse, encodingutf-8-sig) print( 辈分验证表已导出./output/rank_verification.csv)打开 CSV检查所有father的人rank0若张守仁的rank0则其子张明远的rank必须为1不能是0或2若张明远的rank1其子张立新的rank必须为2依此类推。5.2 同辈长幼顺序验证用 pandas groupby idxmin 检查 birth_year 排序稳定性苏式要求“长幼有序”即同辈内birth_year小者在左。用代码验证排序是否被graphviz打乱# 检查每层内 order 是否与 birth_year 一致 for r in ranks: layer core_df[core_df[rank] r].sort_values(order) # 获取实际 birth_year 序列 years layer[birth_year].dropna().tolist() # 若有 birth_year检查是否升序 if len(years) 1: is_sorted all(years[i] years[i1] for i in range(len(years)-1)) print(f✅ rank{r} 层 birth_year 排序{正确 if is_sorted else 异常}) else: print(fℹ️ rank{r} 层无足够 birth_year 数据按姓名字典序排列)若输出“异常”说明 Excel 中birth_year有录入错误如 1920 录成 2019需回溯修正。5.3 继嗣标注完整性验证正则匹配所有 “出继.” 和 “入继.” 字符串最后一步用 Python 打开生成的 PNGOCR 识别文字或直接检查 DOT 源码确认所有继嗣标注无遗漏、无错字# 直接检查 core_df 中 adoption_note 是否全覆盖 missing_adoption core_df[(core_df[is_adopted_in] ! ) | (core_df[is_adopted_out] ! )][adoption_note] if missing_adoption.any(): print(❌ 发现继嗣成员未生成标注请检查 is_adopted_in/out 字段是否含空格或全角字符) else: print(✅ 所有继嗣成员均已标注)真正落地的家谱图不是“看起来像”而是每一处标注、每一根连线、每一层间距都经得起族老拿着放大镜逐条核对。这套方法把 Excel 数据变成可验证、可复用、可批量生成的苏式谱图下次修谱你只需更新 Excel运行一次脚本五代图就自动生成——省下的不是几小时而是修谱人最珍贵的耐心与时间。本文还有配套的精品资源点击获取