Various fixes on tree and removing a member
This commit is contained in:
94
tree.py
94
tree.py
@ -6,49 +6,37 @@ from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
def create_avatar_with_name(user_id, name, avatar_path, output_path):
|
||||
"""Crée une image combinant avatar rond et pseudo en dessous"""
|
||||
try:
|
||||
# Charge l'avatar original
|
||||
avatar = Image.open(avatar_path).convert("RGBA")
|
||||
|
||||
# Redimensionne et arrondit l'avatar
|
||||
avatar = avatar.resize((150, 150))
|
||||
|
||||
mask = Image.new('L', (150, 150), 0)
|
||||
draw = ImageDraw.Draw(mask)
|
||||
draw.ellipse((0, 0, 150, 150), fill=255)
|
||||
rounded_avatar = ImageOps.fit(avatar, mask.size, centering=(0.5, 0.5))
|
||||
rounded_avatar.putalpha(mask)
|
||||
|
||||
# Crée une nouvelle image avec espace pour le texte
|
||||
combined = Image.new('RGBA', (200, 200), (0, 0, 0, 0))
|
||||
combined.paste(rounded_avatar, (25, 0), rounded_avatar)
|
||||
|
||||
# Ajoute le texte
|
||||
|
||||
draw = ImageDraw.Draw(combined)
|
||||
try:
|
||||
font = ImageFont.truetype("arial.ttf", 14)
|
||||
except:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
|
||||
text_width = draw.textlength(name, font=font)
|
||||
draw.text(((200 - text_width) / 2, 160), name, font=font, fill="white")
|
||||
|
||||
|
||||
combined.save(output_path)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Erreur création avatar+texte {user_id}: {e}")
|
||||
return False
|
||||
|
||||
def generate_tree(input_file, output_file, root_id=None): # root_id devient optionnel
|
||||
def generate_tree(input_file, output_file, root_id=None):
|
||||
with open(input_file) as f:
|
||||
data = json.load(f)
|
||||
|
||||
roots = data.get("roots", [root_id] if root_id else [])
|
||||
if not roots:
|
||||
roots = [mid for mid, m in data["members"].items() if not m.get("parents")]
|
||||
|
||||
with open(input_file) as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Configuration du graphe
|
||||
G = pgv.AGraph(
|
||||
directed=True,
|
||||
rankdir="TB",
|
||||
@ -58,7 +46,6 @@ def generate_tree(input_file, output_file, root_id=None): # root_id devient opt
|
||||
splines="true"
|
||||
)
|
||||
|
||||
# Style minimal pour les nœuds
|
||||
node_style = {
|
||||
"shape": "none",
|
||||
"imagescale": "false",
|
||||
@ -68,21 +55,19 @@ def generate_tree(input_file, output_file, root_id=None): # root_id devient opt
|
||||
"fixedsize": "true"
|
||||
}
|
||||
|
||||
# Style des flèches
|
||||
edge_style = {
|
||||
"color": "#FFFFFF",
|
||||
"penwidth": "3",
|
||||
"arrowsize": "1.2"
|
||||
}
|
||||
|
||||
# Dossier temporaire pour les images combinées
|
||||
os.makedirs("temp_avatars", exist_ok=True)
|
||||
|
||||
# Traitement de tous les membres
|
||||
for user_id, info in data["members"].items():
|
||||
original_avatar = f"avatars/{user_id}.png"
|
||||
combined_avatar = f"temp_avatars/combined_{user_id}.png"
|
||||
|
||||
|
||||
if os.path.exists(original_avatar):
|
||||
create_avatar_with_name(
|
||||
user_id,
|
||||
@ -96,37 +81,53 @@ def generate_tree(input_file, output_file, root_id=None): # root_id devient opt
|
||||
**node_style
|
||||
)
|
||||
else:
|
||||
# Fallback si pas d'avatar
|
||||
G.add_node(
|
||||
user_id,
|
||||
label=info["name"],
|
||||
**{**node_style, "fontcolor": "white"}
|
||||
)
|
||||
|
||||
# Organisation hiérarchique
|
||||
levels = {0: [root_id], 1: [], 2: []}
|
||||
|
||||
for user_id, info in data["members"].items():
|
||||
if user_id == root_id:
|
||||
continue
|
||||
|
||||
parents = [p["id"] for p in info.get("parents", [])]
|
||||
if root_id in parents:
|
||||
levels[1].append(user_id)
|
||||
elif any(p in levels[1] for p in parents):
|
||||
levels[2].append(user_id)
|
||||
# Nouveau : Dictionnaire pour forcer les rangs des couples
|
||||
forced_ranks = {}
|
||||
|
||||
# Ajout des partenaires au bon niveau
|
||||
# Traitement des couples
|
||||
for couple in data.get("couples", []):
|
||||
if all(m in data["members"] for m in couple):
|
||||
for member in couple:
|
||||
if member not in levels[1] and any(m in levels[1] for m in couple):
|
||||
levels[1].append(member)
|
||||
member1, member2 = couple
|
||||
|
||||
# Style spécial pour les liens de couple
|
||||
G.add_edge(member1, member2,
|
||||
style="dashed",
|
||||
color="#FF69B4",
|
||||
penwidth="2.5",
|
||||
dir="none")
|
||||
|
||||
# Force les partenaires au même rang
|
||||
partner_group = f"couple_{min(couple)}_{max(couple)}"
|
||||
G.add_subgraph(couple, name=partner_group, rank="same")
|
||||
forced_ranks[member1] = partner_group
|
||||
forced_ranks[member2] = partner_group
|
||||
|
||||
# Organisation hiérarchique améliorée
|
||||
if root_id:
|
||||
roots = [root_id]
|
||||
else:
|
||||
roots = data.get("roots", [k for k,v in data["members"].items() if not v.get("parents")])
|
||||
|
||||
# Nouvelle approche pour les niveaux
|
||||
levels = {}
|
||||
for user_id in data["members"]:
|
||||
gen = data["members"][user_id].get("generation",
|
||||
len(data["members"][user_id].get("parents", [])))
|
||||
if gen not in levels:
|
||||
levels[gen] = []
|
||||
levels[gen].append(user_id)
|
||||
|
||||
# Création des sous-graphes par niveau
|
||||
for level, members in levels.items():
|
||||
if members:
|
||||
G.add_subgraph(members, name=f"rank{level}", rank="same")
|
||||
filtered_members = [m for m in members if m not in forced_ranks]
|
||||
if filtered_members:
|
||||
G.add_subgraph(filtered_members, name=f"rank{level}", rank="same")
|
||||
|
||||
# Connexions parent-enfant
|
||||
for user_id, info in data["members"].items():
|
||||
@ -134,18 +135,9 @@ def generate_tree(input_file, output_file, root_id=None): # root_id devient opt
|
||||
if parent["id"] in data["members"]:
|
||||
G.add_edge(parent["id"], user_id, **edge_style)
|
||||
|
||||
# Connexions des couples
|
||||
for couple in data.get("couples", []):
|
||||
if all(m in data["members"] for m in couple):
|
||||
G.add_edge(couple[0], couple[1],
|
||||
style="dashed",
|
||||
color="#FF69B4",
|
||||
penwidth="2.5",
|
||||
dir="none")
|
||||
|
||||
G.layout(prog="dot")
|
||||
G.draw(output_file)
|
||||
|
||||
# Nettoyage des fichiers temporaires
|
||||
|
||||
# Nettoyage
|
||||
for f in os.listdir("temp_avatars"):
|
||||
os.remove(f"temp_avatars/{f}")
|
||||
|
Reference in New Issue
Block a user