新鲜出炉的用python写的族谱软件-第三版
本帖最后由 553429363 于 2024-11-14 10:29 编辑废话不多说,先上图,上成品,最后上源码!
我用夸克网盘分享了「FamilyTreeAppV3.exe」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
链接:https://pan.quark.cn/s/5ed9f7a15b61
FamilyTreeAppV3.exe https://www.alipan.com/s/RAg797FnAiB 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。
import sys
import pickle
import csv
from importlib.resources.readers import remove_duplicates
from warnings import catch_warnings
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QListWidget, QMessageBox, QFileDialog, QRadioButton, QButtonGroup, QInputDialog
)
from PyQt5.QtGui import QIcon
from graphviz import Digraph
# 定义FamilyMember类,表示族谱中的一个成员
class FamilyMember:
def __init__(self, name, father=None, mother=None, spouse=None, gender='男'):
self.name, self.father, self.mother, self.spouse, self.gender = name, father, mother, spouse, gender
self.children = []
self.generation = 1
# 使用辅助集合保持顺序地去重
def remove_duplicates(lst):
seen = set()
unique_list = []
for item in lst:
if item not in seen:
seen.add(item)
unique_list.append(item)
return unique_list
def add_child(self, child):
li=[]
for i in self.children:
li.append(i)
li.append(child)
li=remove_duplicates(li)
return li
def calculate_generation(self, family_tree):
# 计算代数,递归父亲代数加1,直至没有父亲时返回1
if self.father and self.father in family_tree:
self.generation = family_tree.generation + 1
else:
if self.spouse in family_tree:
self.generation=family_tree.generation
else:
self.generation = 1
# 定义FamilyTreeApp类,继承自QWidget,负责创建图形用户界面和处理用户交互
class FamilyTreeApp(QWidget):
def __init__(self):
super().__init__()
self.family_tree = {}
self.initUI()
def initUI(self):
self.setWindowTitle('族谱软件')
self.setGeometry(100, 100, 600, 400)
self.setWindowIcon(QIcon('icon.ico'))
layout = QVBoxLayout(self)
input_layout = QHBoxLayout()
input_layout.addWidget(QLabel('姓名:'))
self.name_input = QLineEdit(self)
input_layout.addWidget(self.name_input)
input_layout.addWidget(QLabel('父亲:'))
self.father_input = QLineEdit(self)
input_layout.addWidget(self.father_input)
input_layout.addWidget(QLabel('母亲:'))
self.mother_input = QLineEdit(self)
input_layout.addWidget(self.mother_input)
input_layout.addWidget(QLabel('配偶:'))
self.spouse_input = QLineEdit(self)
input_layout.addWidget(self.spouse_input)
input_layout.addWidget(QLabel('儿子:'))
self.son_input = QLineEdit(self)
input_layout.addWidget(self.son_input)
input_layout.addWidget(QLabel('女儿:'))
self.daughter_input = QLineEdit(self)
input_layout.addWidget(self.daughter_input)
layout.addLayout(input_layout)
gender_layout = QHBoxLayout()
self.gender_group = QButtonGroup(self)
self.male_radio = QRadioButton('男', self)
self.female_radio = QRadioButton('女', self)
self.male_radio.setChecked(True)
self.gender_group.addButton(self.male_radio)
self.gender_group.addButton(self.female_radio)
gender_layout.addWidget(QLabel('性别:'))
gender_layout.addWidget(self.male_radio)
gender_layout.addWidget(self.female_radio)
layout.addLayout(gender_layout)
button_layout = QHBoxLayout()
self.add_button = QPushButton('添加成员', self)
self.save_button = QPushButton('保存族谱', self)
self.load_button = QPushButton('加载族谱', self)
self.clear_button = QPushButton('清除族谱', self)
self.print_button = QPushButton('打印族谱', self)
self.new_button = QPushButton('新建族谱', self)
self.export_button = QPushButton('导出CSV', self)
button_layout.addWidget(self.add_button)
button_layout.addWidget(self.save_button)
button_layout.addWidget(self.load_button)
button_layout.addWidget(self.clear_button)
button_layout.addWidget(self.print_button)
button_layout.addWidget(self.new_button)
button_layout.addWidget(self.export_button)
layout.addLayout(button_layout)
self.family_list = QListWidget(self)
layout.addWidget(self.family_list)
self.add_button.clicked.connect(self.add_member)
self.save_button.clicked.connect(self.save_family_tree)
self.load_button.clicked.connect(self.load_family_tree)
self.clear_button.clicked.connect(self.clear_family_tree)
self.print_button.clicked.connect(self.print_family_tree)
self.new_button.clicked.connect(self.new_family_tree)
self.export_button.clicked.connect(self.export_family_tree)
def add_member(self):
name, father, mother, spouse = self.name_input.text(), self.father_input.text(), self.mother_input.text(), self.spouse_input.text()
if father =='':father=None
if mother == '': mother = None
if spouse == '': spouse = None
sons = self.son_input.text().split(',') if self.son_input.text() else []
daughters = self.daughter_input.text().split(',') if self.daughter_input.text() else []
gender = '男' if self.male_radio.isChecked() else '女'
if not name:
QMessageBox.warning(self, '错误', '姓名不能为空')
return
# 检查并添加父亲、母亲、配偶、儿子和女儿
if father and father not in self.family_tree:
self.family_tree = FamilyMember(father,gender='男')
if mother and mother not in self.family_tree:
self.family_tree = FamilyMember(mother,gender='女')
if spouse and spouse not in self.family_tree:
spouse_gender = '女' if gender == '男' else '男'
self.family_tree = FamilyMember(spouse, gender=spouse_gender)
for son in sons:
if son and son not in self.family_tree:
self.family_tree = FamilyMember(son, father=name, gender='男')
if spouse in self.family_tree:self.family_tree.add_child(son)
for daughter in daughters:
if daughter and daughter not in self.family_tree:
self.family_tree = FamilyMember(daughter, father=name, gender='女')
if spouse in self.family_tree:self.family_tree.add_child(daughter)
if name in self.family_tree:
reply = QMessageBox.question(self, '重名提示', f'成员 {name} 已存在,是否更新信息?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
member = self.family_tree
member.father, member.mother, member.spouse, member.gender = father, mother, spouse, gender
member.children=remove_duplicates(sons + daughters)
else:
name = self.get_unique_name(name)
self.family_tree = FamilyMember(name, father, mother, spouse, gender)
self.family_tree.children = remove_duplicates(sons + daughters)
else:
self.family_tree = FamilyMember(name, father, mother, spouse, gender)
self.family_tree.children = remove_duplicates(sons + daughters)
if father in self.family_tree:
self.family_tree.add_child(name)
if mother in self.family_tree:
self.family_tree.add_child(name)
if spouse in self.family_tree:
self.family_tree.spouse = name
# 确保父亲和母亲是配偶
if father and mother and father in self.family_tree and mother in self.family_tree:
self.family_tree.spouse = mother
self.family_tree.spouse = father
for son in sons:
if son in self.family_tree:
if gender=='男':
self.family_tree.father = name
self.family_tree.mother = spouse
else:
self.family_tree.father = spouse
self.family_tree.mother = name
for daughter in daughters:
if daughter in self.family_tree:
if gender == '男':
self.family_tree.father = name
self.family_tree.mother = spouse
else:
self.family_tree.father = spouse
self.family_tree.mother = name
self.update_generations()
self.update_list()
def get_unique_name(self, name):
count = 1
unique_name = f"{name}_{count}"
while unique_name in self.family_tree:
count += 1
unique_name = f"{name}_{count}"
return unique_name
def update_generations(self):
# 先计算所有成员的代数
for member in self.family_tree.values():
member.calculate_generation(self.family_tree)
# 确保夫妻的代数相同,并且以丈夫的代数为准
for member in self.family_tree.values():
if member.gender == '男' and member.spouse and member.spouse in self.family_tree:
self.family_tree.generation = member.generation
# 联动更新所有相关成员的代数
self.propagate_generations()
def propagate_generations(self):
# 使用广度优先搜索(BFS)更新所有相关成员的代数
queue = list(self.family_tree.values())
while queue:
member = queue.pop(0)
old_generation = member.generation
member.calculate_generation(self.family_tree)
if member.generation != old_generation:
for child in member.children:
if child in self.family_tree:
queue.append(self.family_tree)
def update_list(self):
self.family_list.clear()
for member in self.family_tree.values():
spouse_info = f' (配偶: {member.spouse})' if member.spouse else ''
self.family_list.addItem(f'{member.name}{spouse_info} (代数: {member.generation})')
def save_family_tree(self):
file_name, _ = QFileDialog.getSaveFileName(self, '保存族谱', '', 'Pickle Files (*.pkl);;All Files (*)')
if file_name:
with open(file_name, 'wb') as f:
pickle.dump(self.family_tree, f)
def load_family_tree(self):
file_name, _ = QFileDialog.getOpenFileName(self, '加载族谱', '', 'Pickle Files (*.pkl);;All Files (*)')
if file_name:
with open(file_name, 'rb') as f:
self.family_tree = pickle.load(f)
self.update_generations()
self.update_list()
def clear_family_tree(self):
self.family_tree.clear()
self.update_list()
def print_family_tree(self):
if not self.family_tree:
QMessageBox.warning(self, '错误', '族谱为空')
return
dot = Digraph(comment='Family Tree', format='png')
dot.attr('graph', rankdir='TB')
dot.attr('node', fontname='FangSong_GB2312')
for member in self.family_tree.values():
color=self.get_generation_color(member.generation)
#当前家庭夫妻俩节点
if member.gender == '男':
if member.spouse is None:
dot.node(f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知", style='filled', fillcolor=color)
else:
dot.node(f"第{member.generation}代 夫:"+str(member.name) + " 妻:" + str(member.spouse), style='filled', fillcolor=color)
else:
if member.spouse is None:
dot.node(f"第{member.generation}代 夫:未知" + " 妻:" + str(member.name), style='filled',fillcolor=color)
else:
dot.node(f"第{member.generation}代 夫:" + str(member.spouse) + " 妻:"+str(member.name), style='filled',fillcolor=color)
if member.spouse is not None:
if member.father is not None:
if member.mother is not None:#有父亲、母亲和配偶
if member.gender == '男':
dot.edge(f"第{self.family_tree.generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:"+str(member.spouse))
else:
dot.edge(f"第{self.family_tree.generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" +str(member.spouse)+ " 妻:" + str(member.name))
else:#有配偶、父亲没有母亲
if member.gender == '男':
dot.edge(f"第{self.family_tree.generation}代 夫:"+str(member.father)+" 妻:未知", f"第{member.generation}代 夫:"+str(member.name)+" 妻:"+str(member.spouse))
else:
dot.edge(f"第{self.family_tree.generation}代 夫:" + str(member.father) + " 妻:未知",f"第{member.generation}代 夫:"+str(member.spouse)+" 妻:"+str(member.name) )
else:
if member.mother is not None:#有母亲、配偶,没有父亲
if member.gender == '男':
dot.edge(f"第{self.family_tree.generation}代 夫:未知" + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:" + str(member.spouse))
else:
dot.edge(f"第{self.family_tree.generation}代 夫:未知" + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.spouse) + " 妻:" + str(member.name))
else:
print("无父无母")
else:
if member.father is not None:
if member.mother is not None:#有父亲、母亲没有配偶
if member.gender == '男':
dot.edge(f"第{self.family_tree.generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知")
else:
dot.edge(f"第{self.family_tree.generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:未知" + " 妻:" + str(member.name))
else:#有父亲、配偶没有母亲
if member.gender == '男':
dot.edge(f"第{self.family_tree.generation}代 夫:"+str(member.father)+" 妻:未知", f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知")
else:
dot.edge(f"第{self.family_tree.generation}代 夫:" + str(member.father) + " 妻:未知",f"第{member.generation}代 夫:未知" + " 妻:" + str(member.name))
else:
if member.mother is not None:#有母亲,没父亲、配偶
if member.gender == '男':
dot.edge(f"第{self.family_tree.generation}代 夫:未知" + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知" )
else:
dot.edge(f"第{self.family_tree.generation}代 夫:未知" + " 妻:" + str(member.mother),f"第{member.generation}代 夫:未知" + " 妻:" + str(member.name))
else:#全没有
print("无父无母无对象")
file_name, _ = QFileDialog.getSaveFileName(self, '打印族谱', '', 'PNG Files (*.png);;All Files (*)')
if file_name:
dot.render(file_name, view=True)
dot.clear()
def get_generation_color(self, generation):
colors = ['#FFD700', '#FFA500', '#FF8C00', '#FF6347', '#FF4500', '#FF0000']
return colors
def new_family_tree(self):
self.family_tree.clear()
self.update_list()
def export_family_tree(self):
if not self.family_tree:
QMessageBox.warning(self, '错误', '族谱为空')
return
file_name, _ = QFileDialog.getSaveFileName(self, '导出族谱', '', 'CSV Files (*.csv);;All Files (*)')
try:
if file_name:
with open(file_name, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '父亲', '母亲', '配偶', '性别', '代数'])
for member in self.family_tree.values():
writer.writerow()
except :
print('')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = FamilyTreeApp()
ex.show()
sys.exit(app.exec_())
欢迎大家,继续提提意见,之前因为不熟悉版规,也给管理大大造成了困扰,万分抱歉!
scp201 发表于 2024-11-14 11:08
是不是也能通过EXCEL导入搭建呢?
可以可以哈哈 https://www.52pojie.cn/thread-1978103-1-1.html
这个大佬也写了,感觉两位大佬各有所长啊,针不戳 @风之暇想 版主大大 求审核 是不是也能通过EXCEL导入搭建呢?{:301_998:} scp201 发表于 2024-11-14 11:08
是不是也能通过EXCEL导入搭建呢?
以后改一下 这辈子还没见过族谱{:1_925:} 用开源代码制作族谱,很有特色,族谱分支条件讨论得很详细👍
感觉在每一代都对齐的布局中可以把辈分标在画布边缘,使每个节点小巧一些
另外就是代际的箭头是斜的不像很多族谱那样横平竖直再分岔,不过表达意思已经很清楚了,看习惯了就行
(仅供参照哈,我是还没编过族谱的Python和Graphiz小白)
页:
[1]