新鲜出炉的用python写的族谱软件-第三版

查看 70|回复 7
作者:553429363   
废话不多说,先上图,上成品,最后上源码!


微信图片_20241114012723.png (46.25 KB, 下载次数: 0)
下载附件
2024-11-14 10:27 上传



微信图片_20241114100101.png (51.98 KB, 下载次数: 0)
下载附件
2024-11-14 10:27 上传



微信图片_20241114100107.png (20.67 KB, 下载次数: 0)
下载附件
2024-11-14 10:27 上传

我用夸克网盘分享了「FamilyTreeAppV3.exe」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
链接:https://pan.quark.cn/s/5ed9f7a15b61
FamilyTreeAppV3.exe https://www.alipan.com/s/RAg797FnAiB 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。
[Python] 纯文本查看 复制代码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[self.father].generation + 1
        else:
            if self.spouse in family_tree:
                self.generation=family_tree[self.spouse].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[father] = FamilyMember(father,gender='男')
        if mother and mother not in self.family_tree:
            self.family_tree[mother] = FamilyMember(mother,gender='女')
        if spouse and spouse not in self.family_tree:
            spouse_gender = '女' if gender == '男' else '男'
            self.family_tree[spouse] = FamilyMember(spouse, gender=spouse_gender)
        for son in sons:
            if son and son not in self.family_tree:
                self.family_tree[son] = FamilyMember(son, father=name, gender='男')
                if spouse in self.family_tree:self.family_tree[spouse].add_child(son)
        for daughter in daughters:
            if daughter and daughter not in self.family_tree:
                self.family_tree[daughter] = FamilyMember(daughter, father=name, gender='女')
                if spouse in self.family_tree:self.family_tree[spouse].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[name]
                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[name] = FamilyMember(name, father, mother, spouse, gender)
                self.family_tree[name].children = remove_duplicates(sons + daughters)
        else:
            self.family_tree[name] = FamilyMember(name, father, mother, spouse, gender)
            self.family_tree[name].children = remove_duplicates(sons + daughters)
        if father in self.family_tree:
            self.family_tree[father].add_child(name)
        if mother in self.family_tree:
            self.family_tree[mother].add_child(name)
        if spouse in self.family_tree:
            self.family_tree[spouse].spouse = name
        # 确保父亲和母亲是配偶
        if father and mother and father in self.family_tree and mother in self.family_tree:
            self.family_tree[father].spouse = mother
            self.family_tree[mother].spouse = father
        for son in sons:
            if son in self.family_tree:
                if gender=='男':
                    self.family_tree[son].father = name
                    self.family_tree[son].mother = spouse
                else:
                    self.family_tree[son].father = spouse
                    self.family_tree[son].mother = name
        for daughter in daughters:
            if daughter in self.family_tree:
                if gender == '男':
                    self.family_tree[daughter].father = name
                    self.family_tree[daughter].mother = spouse
                else:
                    self.family_tree[daughter].father = spouse
                    self.family_tree[daughter].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[member.spouse].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[child])
    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[member.father].generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:"+str(member.spouse))
                        else:
                            dot.edge(f"第{self.family_tree[member.father].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[member.father].generation}代 夫:"+str(member.father)+" 妻:未知", f"第{member.generation}代 夫:"+str(member.name)+" 妻:"+str(member.spouse))
                        else:
                            dot.edge(f"第{self.family_tree[member.father].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[member.mother].generation}代 夫:未知" + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:" + str(member.spouse))
                        else:
                            dot.edge(f"第{self.family_tree[member.mother].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[member.father].generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知")
                        else:
                            dot.edge(f"第{self.family_tree[member.father].generation}代 夫:" + str(member.father) + " 妻:" + str(member.mother),f"第{member.generation}代 夫:未知" + " 妻:" + str(member.name))
                    else:#有父亲、配偶没有母亲
                        if member.gender == '男':
                            dot.edge(f"第{self.family_tree[member.father].generation}代 夫:"+str(member.father)+" 妻:未知", f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知")
                        else:
                            dot.edge(f"第{self.family_tree[member.father].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[member.mother].generation}代 夫:未知" + " 妻:" + str(member.mother),f"第{member.generation}代 夫:" + str(member.name) + " 妻:未知" )
                        else:
                            dot.edge(f"第{self.family_tree[member.mother].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[generation % len(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([member.name, member.father, member.mother, member.spouse, member.gender, member.generation])
        except :
            print('')
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = FamilyTreeApp()
    ex.show()
    sys.exit(app.exec_())
欢迎大家,继续提提意见,之前因为不熟悉版规,也给管理大大造成了困扰,万分抱歉!

族谱, 配偶

553429363
OP
  


scp201 发表于 2024-11-14 11:08
是不是也能通过EXCEL导入搭建呢?

可以可以  哈哈
lxyx   

https://www.52pojie.cn/thread-1978103-1-1.html
这个大佬也写了,感觉两位大佬各有所长啊,针不戳
553429363
OP
  

@风之暇想 版主大大 求审核
scp201   

是不是也能通过EXCEL导入搭建呢?
553429363
OP
  


scp201 发表于 2024-11-14 11:08
是不是也能通过EXCEL导入搭建呢?

以后改一下
tuhutuhu   

这辈子还没见过族谱
ZnOSpin   

用开源代码制作族谱,很有特色,族谱分支条件讨论得很详细👍
感觉在每一代都对齐的布局中可以把辈分标在画布边缘,使每个节点小巧一些
另外就是代际的箭头是斜的不像很多族谱那样横平竖直再分岔,不过表达意思已经很清楚了,看习惯了就行
(仅供参照哈,我是还没编过族谱的Python和Graphiz小白)
您需要登录后才可以回帖 登录 | 立即注册

返回顶部