Я составлял дерево больших и маленьких членов братства и искал способ автоматизировать его для внесения изменений по мере присоединения большего числа людей. Имена и годы жизни всех людей, больших и маленьких, указаны в электронной таблице Excel. Что я мог бы использовать, чтобы подражать дизайну, который я сделал здесь? В частности, стиль основы и возможность располагать узлы дальше в зависимости от их года.
Это дизайн, который я хочу автоматизировать:
Я пробовал использовать Anytree и Graphviz, но не смог найдите способ имитировать стебли или простое решение для интервалов по годам.
Пример данных:
Имя
Год
Инструмент
Дополнительно
Дополнительно
Экстра
Экстра
Большой
Маленький 1
Маленький 2
Маленький 3
T1P1
1990
Труба
T1P2
T1P2
1991
Труба
< td>
T1P1
T2P1
1997
Труба
T2P2
T2P2
2001
Труба
T2P1
T2P3
T2P4
< td>T2P5
T2P3
2003
Труба
T2P2
< tr>
T2P4
2004
Труба
T2P2
T2P5
2006
Труба
T2P2
T3P1
2000
Труба
T3P2
T3P2
2004
Труба
T3P1
T3P3
T3P4
T3P3
2005
Труба
T3P2
T3P5
T3P6
T3P5
2006
Труба
< td>
T3P3
T3P6
2007
Труба
T3P3
T3P4
2006
Труба
T3P2
T3P7
T3P7
2010
Флейта
T3P4
< /tbody>
Вот мой основной подход используя 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
# Font for the names on the name cards (reduced to size 22)
name_font_size = 22
name_font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", name_font_size)
# Initialize counters for each year (based on the range of years)
year_counters = {year: 0 for year in range(min_year, max_year + 1)}
# Create a list of names from the spreadsheet, split on newlines where appropriate
for i, person_info in enumerate(people_data):
name = person_info[0] # Assuming name is in the first column (column A)
original_name = name
column_b_data = person_info[1] # Column B data (second column)
column_c_data = person_info[2] # Column C data (third column)
# Choose the correct name card template based on Column C
if column_c_data and "Trumpet" not in column_c_data:
# Use the blue name card template if Column C doesn't include "Trumpet"
name_card_template = Image.open("C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/blank_blue_name_card.png")
else:
# Use the default name card template if Column C includes "Trumpet"
name_card_template = Image.open("C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/blank_name_card.png")
if column_b_data:
year_str = str(column_b_data)[:4]
if year_str.isdigit():
year = int(year_str)
year_index = year - min_year # Find the corresponding year index (from 0 to year_range-1)
person_node.year = year
person_node.name = name
# Check if the name contains "VET" or "RAT"
if "VET" in name or "RAT" in name:
# Replace the first space with a newline
name_lines = name.split(' ', 1)
name = name_lines[0] + '\n' + name_lines[1]
elif name == "Special Case":
# Special case for "Special Case"
name_lines = name.split('-')
name = name_lines[0] + '\n' + name_lines[1] # Add newline after the hyphen
else:
# Split on the last space if it doesn't contain "VET" or "RAT"
name_lines = name.split(' ')
if len(name_lines) > 1:
name = ' '.join(name_lines[:-1]) + '\n' + name_lines[-1]
else:
name_lines = [name]
# Create a copy of the name card for each person
name_card_copy = name_card_template.copy()
card_draw = ImageDraw.Draw(name_card_copy)
# Calculate the total height of all the lines combined (with some padding between lines)
line_heights = []
total_text_height = 0
for line in name.split('\n'):
line_bbox = card_draw.textbbox((0, 0), line, font=name_font)
line_height = line_bbox[3] - line_bbox[1]
line_heights.append(line_height)
total_text_height += line_height
# Shift the text up by 8 pixels and calculate the vertical starting position
start_y = (name_card_template.height - total_text_height) // 2 - 6 # Shifted up by 8px
# Draw each line centered horizontally
current_y = start_y
first_line_raised = False # To track if the first line has 'gjpqy' characters
for i, line in enumerate(name.split('\n')):
line_bbox = card_draw.textbbox((0, 0), line, font=name_font)
line_width = line_bbox[2] - line_bbox[0]
# Calculate the horizontal position to center this line
line_x = (name_card_template.width - line_width) // 2
# Draw the line at the correct position
card_draw.text((line_x, current_y), line, font=name_font, fill="black")
if i == 0 and any(char in line for char in 'gjpqy'):
# If the first line contains any of the letters, lower it by 7px (5px padding + 2px extra)
current_y += line_heights[i] + 7 # 5px for space, 2px additional for g, j, p, q, y
first_line_raised = True
elif i == 0:
# If the first line doesn't contain those letters, add 7px space
current_y += line_heights[i] + 7
else:
# For subsequent lines, add the usual space
if first_line_raised:
# If first line was adjusted for 'gjpqy', raise second line by 2px
current_y += line_heights[i] - 2 # Raise second line by 2px
else:
current_y += line_heights[i] + (5 if i == 0 else 0)
# Position for the name card in the appropriate year strip
card_x = 25 + year_text_x + year_text_width # 25px to the right of the year text
card_y = 300 + (strip_height * year_index) + (strip_height - name_card_template.height) // 2 # Vertically center in the strip based on year
# Assign card and y position attributes to each person
person_node.card = name_card_copy
person_node.y = card_y
# print(person_node.y)
# Use the counter for the corresponding year to determine x_offset
x_offset = card_x + year_counters[year] * 170 # Add offset for each subsequent name card
year_counters[year] += 1 # Increment the counter for this year
# print(f"{year_counters[year]}")
card_file_path = os.path.join(cards_dir, f"{original_name}.png")
person_node.card.save(card_file_path)
# Paste the name card onto the image at the calculated position
base_image.paste(name_card_copy, (x_offset, person_node.y), name_card_copy)
# Save the final image with name cards
base_image.save("final_image_with_name_cards_updated.png")
base_image.show()
пример вывода, отражающего фоновую эстетику оригинальной работы
Вот мой подход к графвизу:
import openpyxl
from anytree import Node, RenderTree
import os
from graphviz import Digraph
from PIL import Image
# Create a directory to store the family tree images
trees_dir = "C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/trees"
cards_dir = "C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/cards"
os.makedirs(trees_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
people_dict = {} # Dictionary to hold people by their names
root_nodes = [] # List to hold the root nodes of multiple trees
parents_set = set() # Sets to track parents and children
children_set = set()
parent_child_relationships = {} # Dictionary to track parent-child relationships
# 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]
parent_name = person_info[7]
children_names = person_info[8:14] # Columns I to N for children
if name not in people_dict:
person_node = Node(name)
# If no parent is mentioned, add as a root node
if parent_name:
parent_node = people_dict.get(parent_name, 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
parents_set.add(parent_name)
else:
root_nodes.append(person_node)
people_dict[name] = person_node # Store the person node
# Create child nodes for the person
for child_name in children_names:
if child_name:
if child_name not in people_dict:
child_node = Node(child_name, parent=person_node)
people_dict[child_name] = child_node
children_set.add(child_name)
if child_name not in parent_child_relationships:
parent_child_relationships[child_name] = set()
parent_child_relationships[child_name].add(name)
# Function to generate the family tree graph using Graphviz
def generate_tree_graph(root_node):
graph = Digraph(format='png', engine='dot', strict=True)
def add_node_edges(node):
# Image file path
image_path = os.path.join(cards_dir, f"{node.name}.png") # Assuming each person has a PNG image named after them
if os.path.exists(image_path):
# If the image exists, replace the node with the image, and remove any text label
graph.node(node.name, image=image_path, shape="none", label='')
else:
# Fallback to text if no image is found (this can be further adjusted if needed)
graph.node(node.name, label=node.name, shape='rect')
# Add edges (parent-child relationships)
if node.parent:
graph.edge(node.parent.name, node.name)
for child in node.children:
add_node_edges(child)
add_node_edges(root_node)
return graph
# Generate and save tree images
tree_images = []
for root_node in root_nodes:
tree_graph = generate_tree_graph(root_node)
tree_image_path = os.path.join(trees_dir, f"{root_node.name}_family_tree")
tree_graph.render(tree_image_path, format='png')
tree_images.append(tree_image_path)
# Resize all tree images to be the same size
target_width = 800 # Target width for each tree image
target_height = 600 # Target height for each tree image
resized_images = []
for image_path in tree_images:
image = Image.open(f"{image_path}.png")
resized_images.append(image)
# Create a new image large enough to hold all resized tree images side by side
total_width = target_width * len(resized_images)
max_height = max(image.height for image in resized_images)
# Create a blank white image to paste the resized trees into
combined_image = Image.new('RGB', (total_width, max_height), color='white')
# Paste each resized tree image into the combined image
x_offset = 0
for image in resized_images:
combined_image.paste(image, (x_offset, 0))
x_offset += image.width
# Save the final combined image as a single PNG file
combined_image_path = 'C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/final_combined_family_tree.png'
combined_image.save(combined_image_path)
# Show the final combined image
combined_image.show()
пример вывода, показывающий деревья с использованием правильных визуальных элементов узлов
Я составлял дерево больших и маленьких членов братства и искал способ автоматизировать его для внесения изменений по мере присоединения большего числа людей. Имена и годы жизни всех людей, больших и маленьких, указаны в электронной таблице Excel. Что я мог бы использовать, чтобы подражать дизайну, который я сделал здесь? В частности, стиль основы и возможность располагать узлы дальше в зависимости от их года. Это дизайн, который я хочу автоматизировать: [img]https://i.sstatic.net/8MJxJ53T.png[/img]
Я пробовал использовать Anytree и Graphviz, но не смог найдите способ имитировать стебли или простое решение для интервалов по годам. Пример данных:
Имя Год Инструмент Дополнительно Дополнительно Экстра Экстра Большой Маленький 1 Маленький 2 Маленький 3
T1P1 1990 Труба
T1P2
T1P2 1991 Труба
< td>
T1P1
T2P1 1997 Труба
T2P2
T2P2 2001 Труба
T2P1 T2P3 T2P4 < td>T2P5
T2P3 2003 Труба
T2P2
< tr> T2P4 2004 Труба
T2P2
T2P5 2006 Труба
T2P2
T3P1 2000 Труба
T3P2
T3P2 2004 Труба
T3P1 T3P3 T3P4
T3P3 2005 Труба
T3P2 T3P5 T3P6
T3P5 2006 Труба
< td>
T3P3
T3P6 2007 Труба
T3P3
T3P4 2006 Труба
T3P2 T3P7
T3P7 2010 Флейта
T3P4
< /tbody>
Вот мой основной подход используя Anytree и результаты: [code]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)
# 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)]
# 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
# Font for the names on the name cards (reduced to size 22) name_font_size = 22 name_font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", name_font_size)
# Initialize counters for each year (based on the range of years) year_counters = {year: 0 for year in range(min_year, max_year + 1)}
# Create a list of names from the spreadsheet, split on newlines where appropriate for i, person_info in enumerate(people_data): name = person_info[0] # Assuming name is in the first column (column A) original_name = name column_b_data = person_info[1] # Column B data (second column) column_c_data = person_info[2] # Column C data (third column)
# Choose the correct name card template based on Column C if column_c_data and "Trumpet" not in column_c_data: # Use the blue name card template if Column C doesn't include "Trumpet" name_card_template = Image.open("C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/blank_blue_name_card.png") else: # Use the default name card template if Column C includes "Trumpet" name_card_template = Image.open("C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/blank_name_card.png")
if column_b_data: year_str = str(column_b_data)[:4] if year_str.isdigit(): year = int(year_str) year_index = year - min_year # Find the corresponding year index (from 0 to year_range-1)
person_node.year = year
person_node.name = name
# Check if the name contains "VET" or "RAT" if "VET" in name or "RAT" in name: # Replace the first space with a newline name_lines = name.split(' ', 1) name = name_lines[0] + '\n' + name_lines[1] elif name == "Special Case": # Special case for "Special Case" name_lines = name.split('-') name = name_lines[0] + '\n' + name_lines[1] # Add newline after the hyphen else: # Split on the last space if it doesn't contain "VET" or "RAT" name_lines = name.split(' ') if len(name_lines) > 1: name = ' '.join(name_lines[:-1]) + '\n' + name_lines[-1] else: name_lines = [name]
# Create a copy of the name card for each person name_card_copy = name_card_template.copy() card_draw = ImageDraw.Draw(name_card_copy)
# Calculate the total height of all the lines combined (with some padding between lines) line_heights = [] total_text_height = 0 for line in name.split('\n'): line_bbox = card_draw.textbbox((0, 0), line, font=name_font) line_height = line_bbox[3] - line_bbox[1] line_heights.append(line_height) total_text_height += line_height
# Shift the text up by 8 pixels and calculate the vertical starting position start_y = (name_card_template.height - total_text_height) // 2 - 6 # Shifted up by 8px
# Draw each line centered horizontally current_y = start_y first_line_raised = False # To track if the first line has 'gjpqy' characters for i, line in enumerate(name.split('\n')): line_bbox = card_draw.textbbox((0, 0), line, font=name_font) line_width = line_bbox[2] - line_bbox[0]
# Calculate the horizontal position to center this line line_x = (name_card_template.width - line_width) // 2
# Draw the line at the correct position card_draw.text((line_x, current_y), line, font=name_font, fill="black")
if i == 0 and any(char in line for char in 'gjpqy'): # If the first line contains any of the letters, lower it by 7px (5px padding + 2px extra) current_y += line_heights[i] + 7 # 5px for space, 2px additional for g, j, p, q, y first_line_raised = True elif i == 0: # If the first line doesn't contain those letters, add 7px space current_y += line_heights[i] + 7 else: # For subsequent lines, add the usual space if first_line_raised: # If first line was adjusted for 'gjpqy', raise second line by 2px current_y += line_heights[i] - 2 # Raise second line by 2px else: current_y += line_heights[i] + (5 if i == 0 else 0)
# Position for the name card in the appropriate year strip card_x = 25 + year_text_x + year_text_width # 25px to the right of the year text card_y = 300 + (strip_height * year_index) + (strip_height - name_card_template.height) // 2 # Vertically center in the strip based on year
# Assign card and y position attributes to each person person_node.card = name_card_copy person_node.y = card_y # print(person_node.y)
# Use the counter for the corresponding year to determine x_offset x_offset = card_x + year_counters[year] * 170 # Add offset for each subsequent name card year_counters[year] += 1 # Increment the counter for this year # print(f"{year_counters[year]}")
# Paste the name card onto the image at the calculated position base_image.paste(name_card_copy, (x_offset, person_node.y), name_card_copy)
# Save the final image with name cards base_image.save("final_image_with_name_cards_updated.png") base_image.show() [/code] пример вывода, отражающего фоновую эстетику оригинальной работы Вот мой подход к графвизу: [code]import openpyxl from anytree import Node, RenderTree import os from graphviz import Digraph from PIL import Image
# Create a directory to store the family tree images trees_dir = "C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/trees" cards_dir = "C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/cards" os.makedirs(trees_dir, exist_ok=True)
# 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 people_dict = {} # Dictionary to hold people by their names root_nodes = [] # List to hold the root nodes of multiple trees parents_set = set() # Sets to track parents and children children_set = set() parent_child_relationships = {} # Dictionary to track parent-child relationships
# 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] parent_name = person_info[7] children_names = person_info[8:14] # Columns I to N for children
if name not in people_dict: person_node = Node(name)
# If no parent is mentioned, add as a root node if parent_name: parent_node = people_dict.get(parent_name, 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 parents_set.add(parent_name) else: root_nodes.append(person_node)
people_dict[name] = person_node # Store the person node
# Create child nodes for the person for child_name in children_names: if child_name: if child_name not in people_dict: child_node = Node(child_name, parent=person_node) people_dict[child_name] = child_node children_set.add(child_name)
if child_name not in parent_child_relationships: parent_child_relationships[child_name] = set() parent_child_relationships[child_name].add(name)
# Function to generate the family tree graph using Graphviz def generate_tree_graph(root_node): graph = Digraph(format='png', engine='dot', strict=True)
def add_node_edges(node): # Image file path image_path = os.path.join(cards_dir, f"{node.name}.png") # Assuming each person has a PNG image named after them
if os.path.exists(image_path): # If the image exists, replace the node with the image, and remove any text label graph.node(node.name, image=image_path, shape="none", label='') else: # Fallback to text if no image is found (this can be further adjusted if needed) graph.node(node.name, label=node.name, shape='rect')
# Add edges (parent-child relationships) if node.parent: graph.edge(node.parent.name, node.name)
for child in node.children: add_node_edges(child)
add_node_edges(root_node) return graph
# Generate and save tree images tree_images = [] for root_node in root_nodes: tree_graph = generate_tree_graph(root_node) tree_image_path = os.path.join(trees_dir, f"{root_node.name}_family_tree") tree_graph.render(tree_image_path, format='png') tree_images.append(tree_image_path)
# Resize all tree images to be the same size target_width = 800 # Target width for each tree image target_height = 600 # Target height for each tree image resized_images = []
for image_path in tree_images: image = Image.open(f"{image_path}.png") resized_images.append(image)
# Create a new image large enough to hold all resized tree images side by side total_width = target_width * len(resized_images) max_height = max(image.height for image in resized_images)
# Create a blank white image to paste the resized trees into combined_image = Image.new('RGB', (total_width, max_height), color='white')
# Paste each resized tree image into the combined image x_offset = 0 for image in resized_images: combined_image.paste(image, (x_offset, 0)) x_offset += image.width
# Save the final combined image as a single PNG file combined_image_path = 'C:/Users/Chris Fitz/Documents/Fun/Trumpet History/trumpettree/final_combined_family_tree.png' combined_image.save(combined_image_path)
# Show the final combined image combined_image.show() [/code] пример вывода, показывающий деревья с использованием правильных визуальных элементов узлов
Я составлял дерево больших и малых членов братства и искал способ автоматизировать его для внесения изменений по мере присоединения большего числа людей. Имена и годы жизни всех людей, больших и маленьких, указаны в электронной таблице Excel. Что я...
Я составлял дерево больших и малых членов братства и искал способ автоматизировать его для внесения изменений по мере присоединения большего числа людей. Имена и годы жизни всех людей, больших и маленьких, указаны в электронной таблице Excel. Что я...
Я составлял дерево больших и маленьких членов братства и искал способ автоматизировать его для внесения изменений по мере присоединения большего числа людей. Имена и годы жизни всех людей, больших и маленьких, указаны в электронной таблице Excel....
Я делал дерево сражающихся в братстве биг и литт, и искал способ автоматизировать его для изменений, когда присоединяются все больше людей. Имена и годы каждого, большие и маленькие, находятся в электронной таблице Excel. Что я могу использовать для...
Я делал дерево сражающихся в братстве биг и литт, и искал способ автоматизировать его для изменений, когда присоединяются все больше людей. Имена и годы каждого, большие и маленькие, находятся в электронной таблице Excel. Что я могу использовать для...