add analyse fréquence mots tagcloud
This commit is contained in:
parent
7ae7d5915b
commit
056387013d
9 changed files with 781 additions and 6 deletions
|
@ -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.")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue