You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何自动化生成支持自定义枝干样式与按年份调整节点间距的兄弟会成员关系树状图

如何自动化生成支持自定义枝干样式与按年份调整节点间距的兄弟会成员关系树状图

我一直在制作兄弟会相关的“大哥-小弟”关系树,现在想找个方法自动更新——毕竟会有更多人加入。所有人的姓名、年级、对应的大哥和小弟信息都存在Excel表格里。我想复刻自己手动做的这个设计,特别是枝干样式按年份调整节点间距的功能,有什么工具或方法可以实现吗?

我想要复刻的设计样式

我想要复刻的关系树

我试过用anytreegraphviz,但没找到复刻枝干样式的方法,也没找到简单的按年份调整间距的方案。

示例数据

NameYearInstrumentExtraExtraExtraExtraBigLittle 1Little 2Little 3
T1P11990TrumpetT1P2
T1P21991TrumpetT1P1
T2P11997TrumpetT2P2
T2P22001TrumpetT2P1T2P3T2P4T2P5
T2P32003TrumpetT2P2
T2P42004TrumpetT2P2
T2P52006TrumpetT2P2
T3P12000TrumpetT3P2
T3P22004TrumpetT3P1T3P3T3P4
T3P32005TrumpetT3P2T3P5T3P6
T3P52006TrumpetT3P3
T3P62007TrumpetT3P3
T3P42006TrumpetT3P2T3P7
T3P72010FluteT3P4

我目前的基础实现(基于anytree)

import openpyxl
from PIL import Image, ImageDraw, ImageFont
import re
from anytree import Node, RenderTree
from collections import Counter
import os

# Create a directory to store the individual name card images
cards_dir = "C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/cards"
os.makedirs(cards_dir, exist_ok=True)

# Load the .xlsx file
file_path = 'C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/sampletrees.xlsx'
workbook = openpyxl.load_workbook(file_path)
sheet = workbook.active

# Read the data starting from row 2 to the last row with data (max_row) in columns A to N
people_data = []
for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, min_col=1, max_col=14):
    person_info = [cell.value for cell in row]
    people_data.append(person_info)

# Tree Data Making
# Dictionary to hold people by their names
people_dict = {}

# List to hold the root nodes of multiple trees
root_nodes = []

# Sets to track parents and children
parents_set = set()
children_set = set()

# Dictionary to track parent-child relationships for conflict detection
parent_child_relationships = {}

# List to store the individual trees as objects
family_trees = []  # List to hold each separate family tree

# Iterate over the people data and create nodes for each person
for i, person_info in enumerate(people_data, start=2):  # i starts at 2 for row index
    name = person_info[0]  # Assuming name is in the first column (column A)
    column_b_data = person_info[1]  # Column B data (second column)
    parent_name = person_info[7]  # Column H for parent (8th column)
    children_names = person_info[8:14]  # Columns I to N for children (9th to 14th columns)

    # Check if this name is already in the people_dict
    if name not in people_dict:
        # Create the person node (this is the current node) without column B info at this point
        person_node = Node(name)  # Create the person node with just the name

        # If parent_name is empty, this is a root node for a new tree
        if parent_name:
            if parent_name in people_dict:
                parent_node = people_dict[parent_name]
            else:
                parent_node = Node(parent_name)
                people_dict[parent_name] = parent_node  # Add the parent to the dictionary

            person_node.parent = parent_node  # Set the parent for the current person
            # Add to the parents set
            parents_set.add(parent_name)
        else:
            # If no parent is referenced, this could be the root or top-level node
            root_nodes.append(person_node)  # Add to root_nodes list

        # Store the person node in the dictionary (this ensures we don't create duplicates)
        people_dict[name] = person_node

        # Create child nodes for the person and add them to the children set
        for child_name in children_names:
            if child_name:
                # Create child node without modifying its name with additional info from the parent
                if child_name not in people_dict:
                    child_node = Node(child_name, parent=person_node)
                    people_dict[child_name] = child_node  # Store the child in the dictionary
                children_set.add(child_name)

                # Add the parent-child relationship for conflict checking
                if child_name not in parent_child_relationships:
                    parent_child_relationships[child_name] = set()
                parent_child_relationships[child_name].add(name)

# Print out the family trees for each root node (disconnected trees)
for root_node in root_nodes:
    family_tree = []
    for pre, fill, node in RenderTree(root_node):
        family_tree.append(f"{pre}{node.name}")
    family_trees.append(family_tree)  # Save each tree as a separate list of names
    print(f"\nFamily Tree starting from {root_node.name}:")
    for pre, fill, node in RenderTree(root_node):
        print(f"{pre}{node.name}")

# Tree Chart Making
# Extract the years from the first four characters in Column B
years = []
for person_info in people_data:
    column_b_data = person_info[1]
    if column_b_data:
        year_str = str(column_b_data)[:4]
        if year_str.isdigit():
            years.append(int(year_str))

# Calculate the range of years (from the minimum year to the maximum year)
min_year = min(years) if years else 0
max_year = max(years) if years else 0
year_range = max_year - min_year + 1 if years else 0

# Create a base image with a solid color (header space)
base_width = 5000
base_height = 300 + (100 * year_range)  # Header (300px) + layers of 100px strips based on the year range
base_color = "#B3A369"
base_image = Image.new("RGB", (base_width, base_height), color=base_color)

# Create a drawing context
draw = ImageDraw.Draw(base_image)

# Define the text and font for the header
text = "The YJMB Trumpet Section Family Tree"
font_path = "C:/Windows/Fonts/calibrib.ttf"
font_size = 240
font = ImageFont.truetype(font_path, font_size)

# Get the width and height of the header text using textbbox
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]

# Calculate the position to center the header text horizontally
x = (base_width - text_width) // 2
y = (300 - text_height) // 2  # Vertically center the text in the first 300px

# Add the header text to the image
draw.text((x, y), text, font=font, fill=(255, 255, 255))

# List of colors for the alternating strips
colors = ["#FFFFFF", "#003057", "#FFFFFF", "#B3A369"]
strip_height = 100

# Font for the year text
year_font_size = 60
year_font = ImageFont.truetype(font_path, year_font_size)

# Add the alternating colored strips beneath the header
y_offset = 300  # Start just below the header text
for i in range(year_range):
    strip_color = colors[i % len(colors)]

    # Draw the strip
    draw.rectangle([0, y_offset, base_width, y_offset + strip_height], fill=strip_color)

    # Calculate the text to display (the year for this strip)
    year_text = str(min_year + i)

    # Get the width and height of the year text using textbbox
    bbox = draw.textbbox((0, 0), year_text, font=year_font)
    year_text_width = bbox[2] - bbox[0]
    year_text_height = bbox[3] - bbox[1]

    # Calculate the position to center the year text vertically on the strip
    year_text_x = 25  # Offset 25px from the left edge
    year_text_y = y_offset + (strip_height - year_text_height) // 2 - 5  # Vertically center the text

    # Determine the text color based on the strip color
    year_text_color = "#003057" if strip_color == "#FFFFFF" else "white"

    # Add the year text to the strip
    draw.text((year_text_x, year_text_y), year_text, font=year_font, fill=year_text_color)

    # Move the offset for the next strip
    y_offset += strip_height

备注:内容来源于stack exchange,提问作者Chris Fitzpatrick

火山引擎 最新活动