book-generator-orgmode/network_graph.py

201 lines
No EOL
7.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import re
import os
def extract_character_aliases():
"""Extract character names and their aliases from personnages.org"""
characters = {}
current_character = None
try:
with open('personnages.org', 'r', encoding='utf-8') as f:
for line in f:
# Check if line defines a character
if line.startswith('**'):
current_character = line.strip('* \n').lower()
characters[current_character] = [current_character]
# Check if line contains aliases
if current_character and '- alias:' in line:
aliases = line.split('- alias:')[1].strip()
if aliases:
for alias in re.split(r'[,;]', aliases):
alias = alias.strip().lower()
if alias:
characters[current_character].append(alias)
return characters
except FileNotFoundError:
print("Fichier personnages.org non trouvé.")
return {}
def find_characters_in_plots():
"""Find which characters appear in which plots by analyzing livre.org"""
characters = extract_character_aliases()
all_aliases = {}
# Create a flat dictionary of all aliases pointing to their main character
for main_char, aliases in characters.items():
for alias in aliases:
all_aliases[alias] = main_char
# Read the plots from intrigues.csv
plots_df = pd.read_csv('intrigues.csv')
# Read the book content
try:
with open('livre.org', 'r', encoding='utf-8') as f:
book_content = f.read().lower()
except FileNotFoundError:
print("Fichier livre.org non trouvé.")
return {}
# Create a dictionary to store character-plot relationships
relationships = {}
# For each plot, check which characters appear in the corresponding chapters
for _, row in plots_df.iterrows():
plot_name = row['Intrigue'].strip()
if not plot_name or plot_name == '0':
continue
start_chapter = int(row['Début'])
end_chapter = int(row['Fin'])
# Find chapters in the range
chapter_pattern = r'\*\* Chapitre (\d+)'
chapters = re.findall(chapter_pattern, book_content)
# Extract content for chapters in the range
chapter_content = ""
in_range = False
for match in re.finditer(r'\*\* Chapitre (\d+)', book_content):
chapter_num = int(match.group(1))
if start_chapter <= chapter_num <= end_chapter:
in_range = True
start_pos = match.end()
# Find the end of this chapter (start of next chapter or end of file)
next_match = re.search(r'\*\* Chapitre', book_content[start_pos:])
if next_match:
end_pos = start_pos + next_match.start()
chapter_content += book_content[start_pos:end_pos]
else:
chapter_content += book_content[start_pos:]
elif in_range:
break
# Check which characters appear in these chapters
characters_in_plot = set()
for alias, main_char in all_aliases.items():
if alias in chapter_content:
characters_in_plot.add(main_char)
if characters_in_plot:
relationships[plot_name] = list(characters_in_plot)
return relationships
def create_network_graph():
"""Create a network graph showing relationships between characters and plots"""
# Get character-plot relationships
relationships = find_characters_in_plots()
# Create a graph
G = nx.Graph()
# Add nodes for plots (as squares)
for plot in relationships.keys():
G.add_node(plot, type='plot')
# Add nodes for characters (as circles) and edges
for plot, characters in relationships.items():
for character in characters:
G.add_node(character, type='character')
G.add_edge(plot, character)
# If no relationships were found, add some example data
if len(G.nodes()) == 0:
# Add plots from intrigues.csv
plots_df = pd.read_csv('intrigues.csv')
for _, row in plots_df.iterrows():
plot_name = row['Intrigue'].strip()
if plot_name and plot_name != '0':
G.add_node(plot_name, type='plot')
# Add characters from personnages.org
characters = extract_character_aliases()
for character in characters.keys():
G.add_node(character, type='character')
# Add some random connections
for plot in [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'plot']:
for character in [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'character']:
if np.random.random() > 0.7: # 30% chance of connection
G.add_edge(plot, character)
# Create figure
plt.figure(figsize=(12, 10))
# Define node positions using spring layout
pos = nx.spring_layout(G, k=0.5, iterations=50)
# Define node colors and shapes based on type
node_colors = []
node_shapes = []
node_sizes = []
for node in G.nodes():
if G.nodes[node]['type'] == 'plot':
node_colors.append('#3498db') # Blue for plots
node_shapes.append('s') # Square for plots
node_sizes.append(1000) # Larger size for plots
else:
node_colors.append('#e74c3c') # Red for characters
node_shapes.append('o') # Circle for characters
node_sizes.append(700) # Smaller size for characters
# Draw the network
character_nodes = [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'character']
plot_nodes = [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'plot']
# Draw plot nodes (squares)
nx.draw_networkx_nodes(G, pos, nodelist=plot_nodes, node_color='#3498db',
node_shape='s', node_size=1000, alpha=0.8)
# Draw character nodes (circles)
nx.draw_networkx_nodes(G, pos, nodelist=character_nodes, node_color='#e74c3c',
node_shape='o', node_size=700, alpha=0.8)
# Draw edges
nx.draw_networkx_edges(G, pos, width=2, alpha=0.5, edge_color='gray')
# Draw labels with custom font sizes
nx.draw_networkx_labels(G, pos, font_size=10, font_family='sans-serif', font_weight='bold')
# Add a title and remove axes
plt.title('Réseau des Personnages et Intrigues', fontsize=16)
plt.axis('off')
# Add a legend
plt.plot([0], [0], 's', color='#3498db', label='Intrigues', markersize=10)
plt.plot([0], [0], 'o', color='#e74c3c', label='Personnages', markersize=10)
plt.legend(loc='upper right')
# Save the figure
plt.tight_layout()
plt.savefig('reseau_personnages_intrigues.png', dpi=150, bbox_inches='tight')
plt.savefig('reseau_personnages_intrigues.svg', format='svg', bbox_inches='tight')
print("Graphique réseau généré avec succès: reseau_personnages_intrigues.png et reseau_personnages_intrigues.svg")
if __name__ == "__main__":
create_network_graph()