add analyse fréquence mots tagcloud

This commit is contained in:
Tykayn 2025-08-30 18:57:27 +02:00 committed by tykayn
parent 7ae7d5915b
commit 056387013d
9 changed files with 781 additions and 6 deletions

View file

@ -97,6 +97,109 @@ def process_character_occurrences(csv_file='occurrences_personnages.csv'):
return {"nodes": nodes, "links": links}
def process_character_plot_network(char_csv='occurrences_personnages.csv', plot_csv='intrigues.csv'):
"""
Traite les fichiers d'occurrences des personnages et d'intrigues pour générer
les données du graphique de réseau reliant personnages et intrigues.
"""
# Charger les données des personnages
char_data = load_csv_data(char_csv)
if not char_data:
return None
# Charger les données des intrigues
plot_data = load_csv_data(plot_csv)
if not plot_data or len(plot_data) < 2:
return None
# Extraire les en-têtes (noms des personnages)
char_headers = char_data[0]
characters = char_headers[1:] # Ignorer la première colonne (Chapitre)
# Créer un dictionnaire pour stocker les chapitres où chaque personnage apparaît
character_chapters = {char: [] for char in characters}
# Remplir le dictionnaire avec les chapitres où chaque personnage apparaît
for row in char_data[1:]: # Ignorer la ligne d'en-tête
if len(row) < len(char_headers):
continue
chapter = row[0]
# Extraire le numéro de chapitre si possible
chapter_num = None
try:
# Essayer d'extraire un nombre du nom du chapitre
import re
match = re.search(r'(\d+)', chapter)
if match:
chapter_num = int(match.group(1))
except:
pass
# Si on n'a pas pu extraire un nombre, continuer
if chapter_num is None:
continue
# Trouver les personnages présents dans ce chapitre
for i, count in enumerate(row[1:], 1):
if count and int(count) > 0:
character_chapters[char_headers[i]].append(chapter_num)
# Créer les nœuds pour chaque personnage (groupe 1)
nodes = [{"id": char, "name": char, "group": 1, "type": "character"} for char in characters]
# Créer les nœuds pour chaque intrigue (groupe 2)
for row in plot_data[1:]: # Ignorer la ligne d'en-tête
if len(row) < 3:
continue
try:
start = int(row[0])
end = int(row[1])
name = row[2]
nodes.append({
"id": f"plot_{name}",
"name": name,
"group": 2,
"type": "plot",
"start": start,
"end": end
})
except (ValueError, IndexError):
continue
# Créer les liens entre personnages et intrigues
links = []
# Pour chaque intrigue
for node in nodes:
if node["group"] == 2: # C'est une intrigue
plot_start = node["start"]
plot_end = node["end"]
# Pour chaque personnage
for char_node in nodes:
if char_node["group"] == 1: # C'est un personnage
char_name = char_node["id"]
# Vérifier si le personnage apparaît dans un chapitre couvert par l'intrigue
appears_in_plot = False
for chapter in character_chapters[char_name]:
if plot_start <= chapter <= plot_end:
appears_in_plot = True
break
# Si le personnage apparaît dans l'intrigue, créer un lien
if appears_in_plot:
links.append({
"source": char_name,
"target": node["id"],
"value": 1
})
return {"nodes": nodes, "links": links}
def process_writing_progress(csv_file='suivi_livre.csv'):
"""
Traite le fichier de suivi pour générer les données du graphique de progression.
@ -207,7 +310,7 @@ def create_css():
margin: 20px 0;
}
#network-graph {
#network-graph, #character-plot-network, #plot-timeline {
border: 1px solid #ddd;
border-radius: 4px;
}
@ -241,6 +344,20 @@ def create_css():
color: #2c3e50;
}
/* Styles pour le graphique de réseau personnages-intrigues */
.character text {
font-weight: bold;
fill: #333;
}
.plot text {
fill: #e74c3c;
}
.legend {
font-size: 12px;
}
@media (max-width: 768px) {
.stat-card {
width: calc(50% - 20px);
@ -359,6 +476,179 @@ def create_network_graph_js(network_data):
with open(os.path.join(JS_DIR, 'network-graph.js'), 'w', encoding='utf-8') as f:
f.write(js_content)
def create_character_plot_network_js(network_data):
"""Crée le fichier JavaScript pour le graphique de réseau personnages-intrigues."""
js_content = f"""
// Données du graphique de réseau personnages-intrigues
const characterPlotData = {json.dumps(network_data)};
// Fonction pour initialiser le graphique de réseau personnages-intrigues
function initCharacterPlotNetwork() {{
const width = document.getElementById('character-plot-network').clientWidth;
const height = 400;
// Créer le SVG
const svg = d3.select("#character-plot-network")
.append("svg")
.attr("width", width)
.attr("height", height);
// Définir les couleurs pour les différents types de nœuds
const nodeColors = {{
character: "#69b3a2", // Vert pour les personnages
plot: "#e74c3c" // Rouge pour les intrigues
}};
// Créer la simulation de force
const simulation = d3.forceSimulation(characterPlotData.nodes)
.force("link", d3.forceLink(characterPlotData.links).id(d => d.id).distance(150))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(30));
// Créer les liens
const link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(characterPlotData.links)
.enter().append("line")
.attr("stroke-width", d => Math.sqrt(d.value))
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6);
// Créer les nœuds
const node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(characterPlotData.nodes)
.enter().append("g")
.attr("class", d => d.type)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Ajouter des cercles pour les nœuds
node.append("circle")
.attr("r", d => d.type === "character" ? 8 : 12)
.attr("fill", d => nodeColors[d.type]);
// Ajouter des étiquettes pour les nœuds
// Les étiquettes des personnages sont toujours visibles
node.filter(d => d.type === "character")
.append("text")
.text(d => d.name)
.attr("x", 12)
.attr("y", 3)
.style("font-size", "12px")
.style("font-weight", "bold");
// Les étiquettes des intrigues ne sont visibles que lors du survol
const plotLabels = node.filter(d => d.type === "plot")
.append("text")
.text(d => d.name)
.attr("x", 12)
.attr("y", 3)
.style("font-size", "12px")
.style("font-weight", "bold")
.style("fill", "#e74c3c")
.style("opacity", 0); // Initialement invisible
// Ajouter des événements de survol pour les nœuds d'intrigue
node.filter(d => d.type === "plot")
.on("mouseover", function(event, d) {{
// Rendre l'étiquette visible
d3.select(this).select("text").style("opacity", 1);
// Mettre en évidence les liens connectés
link.style("stroke", l =>
l.source.id === d.id || l.target.id === d.id ? "#e74c3c" : "#999")
.style("stroke-opacity", l =>
l.source.id === d.id || l.target.id === d.id ? 1 : 0.2)
.style("stroke-width", l =>
l.source.id === d.id || l.target.id === d.id ? Math.sqrt(l.value) * 2 : Math.sqrt(l.value));
// Mettre en évidence les personnages connectés
node.style("opacity", n =>
n.id === d.id || characterPlotData.links.some(l =>
(l.source.id === d.id && l.target.id === n.id) ||
(l.target.id === d.id && l.source.id === n.id)) ? 1 : 0.4);
}})
.on("mouseout", function() {{
// Cacher l'étiquette
d3.select(this).select("text").style("opacity", 0);
// Restaurer les liens
link.style("stroke", "#999")
.style("stroke-opacity", 0.6)
.style("stroke-width", d => Math.sqrt(d.value));
// Restaurer les nœuds
node.style("opacity", 1);
}});
// Mettre à jour la position des éléments à chaque tick de la simulation
simulation.on("tick", () => {{
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("transform", d => `translate(${{d.x}},${{d.y}})`);
}});
// Fonctions pour le drag & drop
function dragstarted(event, d) {{
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}}
function dragged(event, d) {{
d.fx = event.x;
d.fy = event.y;
}}
function dragended(event, d) {{
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}}
// Ajouter une légende
const legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(20, 20)");
// Légende pour les personnages
legend.append("circle")
.attr("r", 6)
.attr("fill", nodeColors.character);
legend.append("text")
.attr("x", 15)
.attr("y", 4)
.text("Personnages")
.style("font-size", "12px");
// Légende pour les intrigues
legend.append("circle")
.attr("r", 6)
.attr("fill", nodeColors.plot)
.attr("transform", "translate(0, 20)");
legend.append("text")
.attr("x", 15)
.attr("y", 24)
.text("Intrigues (survoler pour voir le nom)")
.style("font-size", "12px");
}}
// Initialiser le graphique quand la page est chargée
document.addEventListener('DOMContentLoaded', initCharacterPlotNetwork);
"""
with open(os.path.join(JS_DIR, 'character-plot-network.js'), 'w', encoding='utf-8') as f:
f.write(js_content)
def create_progress_chart_js(progress_data):
"""Crée le fichier JavaScript pour le graphique de progression."""
js_content = f"""
@ -536,7 +826,7 @@ def create_plot_timeline_js(plot_data):
with open(os.path.join(JS_DIR, 'plot-timeline.js'), 'w', encoding='utf-8') as f:
f.write(js_content)
def create_dashboard_html(network_data, progress_data, plot_data):
def create_dashboard_html(network_data, progress_data, plot_data, character_plot_data=None):
"""Crée le fichier HTML pour le tableau de bord."""
# Calculer quelques statistiques pour afficher
total_words = progress_data['words'][-1] if progress_data['words'] else 0
@ -596,6 +886,12 @@ def create_dashboard_html(network_data, progress_data, plot_data):
<div class="chart-container" id="network-graph"></div>
</div>
<div class="dashboard-section">
<h2>Réseau Personnages-Intrigues</h2>
<p>Cliquez et faites glisser les nœuds pour réorganiser le graphique. Survolez les nœuds d'intrigue pour voir leur nom.</p>
<div class="chart-container" id="character-plot-network"></div>
</div>
<div class="dashboard-section">
<h2>Chronologie des Intrigues</h2>
<div class="chart-container" id="plot-timeline"></div>
@ -607,6 +903,7 @@ def create_dashboard_html(network_data, progress_data, plot_data):
</footer>
<script src="static/js/network-graph.js"></script>
<script src="static/js/character-plot-network.js"></script>
<script src="static/js/progress-chart.js"></script>
<script src="static/js/plot-timeline.js"></script>
</body>
@ -624,6 +921,7 @@ def main():
network_data = process_character_occurrences()
progress_data = process_writing_progress()
plot_data = process_plot_timeline()
character_plot_data = process_character_plot_network()
# Vérifier si les données sont disponibles
if not network_data:
@ -637,15 +935,20 @@ def main():
if not plot_data:
plot_data = []
print("Avertissement: Données d'intrigues non disponibles.")
if not character_plot_data:
character_plot_data = {"nodes": [], "links": []}
print("Avertissement: Données de réseau personnages-intrigues non disponibles.")
# Créer les fichiers CSS et JS
create_css()
create_network_graph_js(network_data)
create_character_plot_network_js(character_plot_data)
create_progress_chart_js(progress_data)
create_plot_timeline_js(plot_data)
# Créer le fichier HTML principal
create_dashboard_html(network_data, progress_data, plot_data)
create_dashboard_html(network_data, progress_data, plot_data, character_plot_data)
print(f"Tableau de bord généré avec succès dans {os.path.join(BUILD_DIR, 'dashboard.html')}")
print("Ouvrez ce fichier dans votre navigateur pour voir le tableau de bord.")