470 lines
No EOL
18 KiB
Python
Executable file
470 lines
No EOL
18 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Script d'analyse des enchaînements d'intrigue dans le livre.
|
|
Ce script:
|
|
1. Analyse le CSV des intrigues
|
|
2. Attribue une couleur à chaque ligne d'intrigue selon un gradient rouge-vert (rouge pour le début, vert pour la fin)
|
|
3. Réalise un graphique avec une seule ligne faite de segments colorés
|
|
4. Sauvegarde le graphique en PNG et SVG
|
|
5. Crée une page web interactive dans le dossier build
|
|
"""
|
|
|
|
import pandas as pd
|
|
import matplotlib.pyplot as plt
|
|
import matplotlib.colors as mcolors
|
|
import numpy as np
|
|
import os
|
|
import json
|
|
from matplotlib.colors import to_hex
|
|
import shutil
|
|
|
|
# Créer le dossier build s'il n'existe pas
|
|
if not os.path.exists('build'):
|
|
os.makedirs('build')
|
|
|
|
# Fonction pour générer un gradient de couleurs du rouge au vert
|
|
def generate_red_to_green_gradient(n):
|
|
"""Génère n couleurs dans un gradient allant du rouge au vert"""
|
|
# Utiliser un gradient de rouge (début) à vert (fin)
|
|
colors = []
|
|
for i in range(n):
|
|
# Calculer la position dans le gradient (0 = rouge, 1 = vert)
|
|
t = i / max(1, n - 1)
|
|
|
|
# Rouge: diminue de 1 à 0
|
|
r = 1.0 - t
|
|
# Vert: augmente de 0 à 1
|
|
g = t
|
|
# Bleu: toujours à 0
|
|
b = 0.0
|
|
|
|
colors.append(np.array([r, g, b, 1.0]))
|
|
|
|
return colors
|
|
|
|
# Lire le fichier CSV des intrigues
|
|
df = pd.read_csv('intrigues.csv')
|
|
|
|
# Trier les intrigues par début
|
|
df = df.sort_values(by=['Début', 'Fin'])
|
|
|
|
# Identifier les intrigues principales et les sous-intrigues
|
|
# Une sous-intrigue est contenue dans une autre intrigue si son début et sa fin
|
|
# sont compris dans l'intervalle de l'intrigue parente
|
|
def identify_subplots(df):
|
|
"""Identifie les relations parent-enfant entre les intrigues"""
|
|
# Initialiser une colonne pour le parent
|
|
df['Parent'] = None
|
|
|
|
# Pour chaque intrigue, trouver son parent (s'il existe)
|
|
for i, row in df.iterrows():
|
|
# Chercher parmi toutes les autres intrigues
|
|
for j, potential_parent in df.iterrows():
|
|
if i != j: # Ne pas comparer avec soi-même
|
|
# Si l'intrigue est contenue dans une autre
|
|
if (row['Début'] >= potential_parent['Début'] and
|
|
row['Fin'] <= potential_parent['Fin'] and
|
|
# S'assurer que ce n'est pas exactement le même intervalle
|
|
not (row['Début'] == potential_parent['Début'] and
|
|
row['Fin'] == potential_parent['Fin'])):
|
|
# Si on a déjà trouvé un parent, prendre le plus spécifique
|
|
if df.at[i, 'Parent'] is not None:
|
|
current_parent_idx = df.index[df['Intrigue'] == df.at[i, 'Parent']].tolist()[0]
|
|
current_parent = df.iloc[current_parent_idx]
|
|
# Le parent le plus spécifique est celui avec l'intervalle le plus court
|
|
if ((potential_parent['Fin'] - potential_parent['Début']) <
|
|
(current_parent['Fin'] - current_parent['Début'])):
|
|
df.at[i, 'Parent'] = potential_parent['Intrigue']
|
|
else:
|
|
df.at[i, 'Parent'] = potential_parent['Intrigue']
|
|
|
|
return df
|
|
|
|
# Appliquer la fonction pour identifier les sous-intrigues
|
|
df = identify_subplots(df)
|
|
|
|
# Organiser les intrigues en groupes basés sur les relations parent-enfant
|
|
def organize_plots_by_hierarchy(df):
|
|
# Créer un dictionnaire pour stocker les groupes d'intrigues
|
|
plot_groups = {}
|
|
group_id = 0
|
|
|
|
# D'abord, identifier toutes les intrigues principales (sans parent)
|
|
main_plots = df[df['Parent'].isna()]['Intrigue'].tolist()
|
|
|
|
# Pour chaque intrigue principale, créer un groupe avec ses sous-intrigues
|
|
for main_plot in main_plots:
|
|
group = [main_plot]
|
|
# Trouver toutes les sous-intrigues directes
|
|
subplots = df[df['Parent'] == main_plot]['Intrigue'].tolist()
|
|
group.extend(subplots)
|
|
|
|
# Trouver les sous-intrigues de niveau 2 (sous-intrigues des sous-intrigues)
|
|
for subplot in subplots:
|
|
sub_subplots = df[df['Parent'] == subplot]['Intrigue'].tolist()
|
|
group.extend(sub_subplots)
|
|
|
|
plot_groups[group_id] = group
|
|
group_id += 1
|
|
|
|
# Créer un dictionnaire qui associe chaque intrigue à son groupe
|
|
plot_to_group = {}
|
|
for group_id, plots in plot_groups.items():
|
|
for plot in plots:
|
|
plot_to_group[plot] = group_id
|
|
|
|
return plot_groups, plot_to_group
|
|
|
|
# Déterminer les valeurs min et max pour les chapitres
|
|
min_chapter = df['Début'].min()
|
|
max_chapter = df['Fin'].max()
|
|
chapter_range = max_chapter - min_chapter
|
|
|
|
# Créer un dictionnaire de couleurs pour chaque intrigue
|
|
color_dict = {}
|
|
|
|
# Trier les intrigues par position dans l'histoire (début du chapitre)
|
|
sorted_intrigues = df.sort_values(by='Début')
|
|
|
|
# Générer un gradient de couleurs du rouge au vert
|
|
gradient_colors = generate_red_to_green_gradient(len(df))
|
|
|
|
# Assigner les couleurs aux intrigues en fonction de leur position dans l'histoire
|
|
for i, (_, row) in enumerate(sorted_intrigues.iterrows()):
|
|
intrigue = row['Intrigue']
|
|
# Utiliser la position normalisée dans l'histoire pour déterminer la couleur
|
|
position = (row['Début'] - min_chapter) / chapter_range if chapter_range > 0 else 0
|
|
# Assigner la couleur du gradient correspondant à la position
|
|
color_index = min(int(position * (len(gradient_colors) - 1)), len(gradient_colors) - 1)
|
|
color_dict[intrigue] = to_hex(gradient_colors[i])
|
|
|
|
# Créer un graphique avec une ligne unique composée de segments colorés
|
|
plt.figure(figsize=(12, 4))
|
|
|
|
# Tracer chaque segment d'intrigue
|
|
for i, row in df.iterrows():
|
|
plt.plot([row['Début'], row['Fin']], [1, 1], linewidth=10,
|
|
color=color_dict[row['Intrigue']],
|
|
solid_capstyle='butt',
|
|
label=row['Intrigue'])
|
|
|
|
# Ajouter des marqueurs pour les points de début et fin
|
|
for i, row in df.iterrows():
|
|
plt.plot(row['Début'], 1, 'o', color='black', markersize=5)
|
|
plt.plot(row['Fin'], 1, 'o', color='black', markersize=5)
|
|
|
|
# Configurer les axes
|
|
plt.yticks([]) # Cacher l'axe y
|
|
plt.xlabel('Chapitres')
|
|
plt.title('Enchaînement des intrigues')
|
|
|
|
# Ajouter une légende unique pour chaque intrigue
|
|
handles, labels = plt.gca().get_legend_handles_labels()
|
|
by_label = dict(zip(labels, handles))
|
|
plt.legend(by_label.values(), by_label.keys(), loc='upper center',
|
|
bbox_to_anchor=(0.5, -0.15), ncol=3)
|
|
|
|
# Ajuster les marges
|
|
plt.tight_layout()
|
|
|
|
# Sauvegarder en PNG et SVG
|
|
plt.savefig('build/enchaînement_intrigues.png', dpi=300, bbox_inches='tight')
|
|
plt.savefig('build/enchaînement_intrigues.svg', bbox_inches='tight')
|
|
|
|
# Créer un diagramme de Gantt (comme dans gantt_parser.py mais avec des couleurs)
|
|
fig, ax = plt.subplots(figsize=(12, 8))
|
|
|
|
# Tracer chaque intrigue avec sa couleur
|
|
for i, row in df.iterrows():
|
|
ax.plot([row['Début'], row['Fin']], [i, i], '-',
|
|
color=color_dict[row['Intrigue']],
|
|
linewidth=10)
|
|
ax.plot([row['Début'], row['Début']], [i-0.1, i+0.1], 'ko')
|
|
ax.plot([row['Fin'], row['Fin']], [i-0.1, i+0.1], 'ko')
|
|
|
|
# Configurer les axes
|
|
ax.set_yticks(range(len(df)))
|
|
ax.set_yticklabels(df['Intrigue'])
|
|
ax.set_xticks(range(int(df['Début'].min()), int(df['Fin'].max())+1))
|
|
ax.set_xticklabels(ax.get_xticks())
|
|
ax.set_xlabel('Chapitres')
|
|
ax.set_title('Diagramme de Gantt des intrigues')
|
|
|
|
# Ajuster les marges
|
|
plt.tight_layout()
|
|
|
|
# Sauvegarder en PNG et SVG
|
|
plt.savefig('build/gantt_intrigues.png', dpi=300, bbox_inches='tight')
|
|
plt.savefig('build/gantt_intrigues.svg', bbox_inches='tight')
|
|
|
|
# Préparer les données pour la page web interactive
|
|
web_data = []
|
|
for i, row in df.iterrows():
|
|
web_data.append({
|
|
'intrigue': row['Intrigue'],
|
|
'debut': int(row['Début']),
|
|
'fin': int(row['Fin']),
|
|
'color': color_dict[row['Intrigue']],
|
|
'parent': row['Parent']
|
|
})
|
|
|
|
# Sauvegarder les données en JSON
|
|
with open('build/intrigues_data.json', 'w', encoding='utf-8') as f:
|
|
json.dump(web_data, f, ensure_ascii=False, indent=2)
|
|
|
|
# Créer la page HTML interactive pour les intrigues
|
|
html_content = """<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Analyse des Intrigues</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
}
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background-color: white;
|
|
padding: 20px;
|
|
border-radius: 5px;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
|
}
|
|
h1, h2 {
|
|
color: #333;
|
|
}
|
|
.plot-timeline {
|
|
height: 100px;
|
|
position: relative;
|
|
margin: 30px 0;
|
|
background-color: #eee;
|
|
border-radius: 5px;
|
|
}
|
|
.plot-segment {
|
|
position: absolute;
|
|
height: 30px;
|
|
top: 35px;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
.plot-segment:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 5px 10px rgba(0,0,0,0.2);
|
|
}
|
|
.timeline-marker {
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 1px;
|
|
height: 10px;
|
|
background-color: #333;
|
|
}
|
|
.timeline-label {
|
|
position: absolute;
|
|
bottom: -20px;
|
|
transform: translateX(-50%);
|
|
font-size: 12px;
|
|
}
|
|
.plot-list {
|
|
margin-top: 50px;
|
|
}
|
|
.plot-item {
|
|
margin-bottom: 10px;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
}
|
|
.subplots {
|
|
margin-left: 30px;
|
|
}
|
|
.plot-info {
|
|
display: none;
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
background-color: #f9f9f9;
|
|
border-radius: 5px;
|
|
border-left: 5px solid #333;
|
|
}
|
|
.images {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
margin-top: 30px;
|
|
}
|
|
img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 5px;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Analyse des Intrigues</h1>
|
|
|
|
<h2>Enchaînement des intrigues</h2>
|
|
<div id="plot-timeline" class="plot-timeline"></div>
|
|
<div id="plot-info" class="plot-info"></div>
|
|
|
|
<h2>Liste des intrigues</h2>
|
|
<div id="plot-list" class="plot-list"></div>
|
|
|
|
<div class="images">
|
|
<h2>Visualisations</h2>
|
|
<div>
|
|
<h3>Enchaînement des intrigues</h3>
|
|
<img src="enchaînement_intrigues.png" alt="Enchaînement des intrigues">
|
|
</div>
|
|
<div>
|
|
<h3>Diagramme de Gantt des intrigues</h3>
|
|
<img src="gantt_intrigues.png" alt="Diagramme de Gantt des intrigues">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Charger les données
|
|
fetch('intrigues_data.json')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Trouver les valeurs min et max pour l'échelle
|
|
const minChapter = Math.min(...data.map(item => item.debut));
|
|
const maxChapter = Math.max(...data.map(item => item.fin));
|
|
const timelineWidth = document.getElementById('plot-timeline').offsetWidth;
|
|
|
|
// Fonction pour convertir un numéro de chapitre en position X
|
|
function chapterToX(chapter) {
|
|
return ((chapter - minChapter) / (maxChapter - minChapter)) * timelineWidth;
|
|
}
|
|
|
|
// Ajouter les marqueurs de chapitre
|
|
const timeline = document.getElementById('plot-timeline');
|
|
for (let i = minChapter; i <= maxChapter; i++) {
|
|
const marker = document.createElement('div');
|
|
marker.className = 'timeline-marker';
|
|
marker.style.left = chapterToX(i) + 'px';
|
|
|
|
const label = document.createElement('div');
|
|
label.className = 'timeline-label';
|
|
label.textContent = i;
|
|
label.style.left = chapterToX(i) + 'px';
|
|
|
|
timeline.appendChild(marker);
|
|
timeline.appendChild(label);
|
|
}
|
|
|
|
// Ajouter les segments d'intrigue
|
|
data.forEach(plot => {
|
|
const segment = document.createElement('div');
|
|
segment.className = 'plot-segment';
|
|
segment.style.left = chapterToX(plot.debut) + 'px';
|
|
segment.style.width = (chapterToX(plot.fin) - chapterToX(plot.debut)) + 'px';
|
|
segment.style.backgroundColor = plot.color;
|
|
segment.setAttribute('data-intrigue', plot.intrigue);
|
|
|
|
// Ajouter un événement au survol
|
|
segment.addEventListener('mouseover', () => {
|
|
const plotInfo = document.getElementById('plot-info');
|
|
plotInfo.style.display = 'block';
|
|
plotInfo.style.borderLeftColor = plot.color;
|
|
plotInfo.innerHTML = `
|
|
<h3>${plot.intrigue}</h3>
|
|
<p>Chapitres ${plot.debut} à ${plot.fin}</p>
|
|
${plot.parent ? `<p>Fait partie de: ${plot.parent}</p>` : ''}
|
|
`;
|
|
});
|
|
|
|
segment.addEventListener('mouseout', () => {
|
|
document.getElementById('plot-info').style.display = 'none';
|
|
});
|
|
|
|
timeline.appendChild(segment);
|
|
});
|
|
|
|
// Organiser les intrigues en hiérarchie
|
|
const plotsWithoutParent = data.filter(plot => !plot.parent);
|
|
const plotList = document.getElementById('plot-list');
|
|
|
|
// Fonction récursive pour afficher les intrigues et leurs sous-intrigues
|
|
function displayPlots(plots, container, level = 0) {
|
|
plots.forEach(plot => {
|
|
const plotItem = document.createElement('div');
|
|
plotItem.className = 'plot-item';
|
|
plotItem.style.backgroundColor = plot.color + '33'; // Ajouter transparence
|
|
plotItem.style.borderLeft = `5px solid ${plot.color}`;
|
|
|
|
plotItem.innerHTML = `
|
|
<h3>${plot.intrigue}</h3>
|
|
<p>Chapitres ${plot.debut} à ${plot.fin}</p>
|
|
`;
|
|
|
|
container.appendChild(plotItem);
|
|
|
|
// Trouver les sous-intrigues
|
|
const subplots = data.filter(subPlot => subPlot.parent === plot.intrigue);
|
|
|
|
if (subplots.length > 0) {
|
|
const subplotsContainer = document.createElement('div');
|
|
subplotsContainer.className = 'subplots';
|
|
plotItem.appendChild(subplotsContainer);
|
|
|
|
displayPlots(subplots, subplotsContainer, level + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Afficher les intrigues principales et leurs sous-intrigues
|
|
displayPlots(plotsWithoutParent, plotList);
|
|
})
|
|
.catch(error => console.error('Erreur lors du chargement des données:', error));
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Écrire le fichier HTML pour les intrigues
|
|
with open('build/intrigues.html', 'w', encoding='utf-8') as f:
|
|
f.write(html_content)
|
|
|
|
# Supprimer l'ancien index.html s'il existe
|
|
if os.path.exists('build/index.html'):
|
|
os.remove('build/index.html')
|
|
|
|
# Mettre à jour le dashboard.html pour inclure un lien vers la page des intrigues
|
|
# et ajouter les visualisations d'intrigues
|
|
if os.path.exists('build/dashboard.html'):
|
|
with open('build/dashboard.html', 'r', encoding='utf-8') as f:
|
|
dashboard_content = f.read()
|
|
|
|
# Ajouter un lien vers la page des intrigues dans la section des intrigues
|
|
if '<div class="dashboard-section">\n <h2>Chronologie des Intrigues</h2>' in dashboard_content:
|
|
dashboard_content = dashboard_content.replace(
|
|
'<div class="dashboard-section">\n <h2>Chronologie des Intrigues</h2>',
|
|
'<div class="dashboard-section">\n <h2>Chronologie des Intrigues</h2>\n <p><a href="intrigues.html">Voir l\'analyse détaillée des intrigues</a></p>'
|
|
)
|
|
|
|
# Ajouter les nouvelles visualisations à la section des intrigues
|
|
if '<div class="chart-container" id="plot-timeline"></div>' in dashboard_content:
|
|
dashboard_content = dashboard_content.replace(
|
|
'<div class="chart-container" id="plot-timeline"></div>',
|
|
'<div class="chart-container" id="plot-timeline"></div>\n <h3>Enchaînement des intrigues</h3>\n <div class="chart-container">\n <img src="enchaînement_intrigues.png" alt="Enchaînement des intrigues" style="max-width:100%;">\n </div>\n <h3>Diagramme de Gantt des intrigues</h3>\n <div class="chart-container">\n <img src="gantt_intrigues.png" alt="Diagramme de Gantt des intrigues" style="max-width:100%;">\n </div>'
|
|
)
|
|
|
|
# Écrire le fichier dashboard mis à jour
|
|
with open('build/dashboard.html', 'w', encoding='utf-8') as f:
|
|
f.write(dashboard_content)
|
|
|
|
print("Analyse des intrigues terminée.")
|
|
print("Fichiers générés dans le dossier 'build':")
|
|
print("- enchaînement_intrigues.png")
|
|
print("- enchaînement_intrigues.svg")
|
|
print("- gantt_intrigues.png")
|
|
print("- gantt_intrigues.svg")
|
|
print("- intrigues_data.json")
|
|
print("- intrigues.html") |