File size: 7,971 Bytes
64b5d29 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# src/visualization/plotting.py (Ağ Metrikleri ile Görselleştirme Güncellendi)
import networkx as nx
from pyvis.network import Network
import logging
from pathlib import Path
import pandas as pd
import random # Renk paleti için
# Yerel modüller
from src.data_management import storage
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Görselleştirme dosyalarının kaydedileceği yer
OUTPUT_DIR = Path("output/graphs")
DEFAULT_GRAPH_FILENAME = "concept_network"
# Analiz sonuçları dosyasının adı (storage'dan da alınabilirdi)
DEFAULT_ANALYSIS_FILENAME = storage.NETWORK_ANALYSIS_FILENAME
# Basit bir renk paleti (daha fazla renk eklenebilir veya matplotlib colormap kullanılabilir)
# Viridis, tab10, Set3 gibi paletler iyi çalışır
# Örnek: import matplotlib.cm as cm; colors = [cm.tab10(i) for i in range(10)]
DEFAULT_COLORS = [
"#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
]
def get_color_for_community(community_id, colors=DEFAULT_COLORS):
""" Verilen community ID için paletten bir renk döndürür. """
if community_id < 0 or community_id is None or pd.isna(community_id): # Topluluk yoksa veya geçersizse
return "#CCCCCC" # Gri
return colors[int(community_id) % len(colors)] # Modulo ile renk tekrarı
def scale_value(value, min_val=0, max_val=1, new_min=10, new_max=50):
""" Bir değeri belirli bir aralığa ölçekler (örn: merkeziyet -> düğüm boyutu). """
if max_val == min_val or value is None or pd.isna(value): # Bölme hatasını veya None değerini engelle
return new_min # Veya ortalama bir değer?
# Ölçekleme: (value - min) / (max - min) * (new_max - new_min) + new_min
scaled = ((value - min_val) / (max_val - min_val)) * (new_max - new_min) + new_min
return max(new_min, min(scaled, new_max)) # Sonuçların min/max arasında kalmasını sağla
def visualize_network(graph: nx.Graph | None = None,
graph_filename: str = DEFAULT_GRAPH_FILENAME,
analysis_filename: str = DEFAULT_ANALYSIS_FILENAME,
output_filename: str = "concept_network_visualization.html",
show_buttons: bool = True,
physics_solver: str = 'barnesHut',
size_metric: str = 'degree_centrality', # Boyut için kullanılacak metrik
color_metric: str = 'community_id', # Renk için kullanılacak metrik
height: str = "800px",
width: str = "100%"
) -> str | None:
"""
Ağ grafını Pyvis ile görselleştirir. Düğüm boyutu ve rengi için ağ
analizi metriklerini kullanır.
"""
if graph is None:
logging.info(f"Graf sağlanmadı, '{graph_filename}.pkl' dosyasından yükleniyor...")
graph = storage.load_network(graph_filename)
if graph is None or not isinstance(graph, nx.Graph) or graph.number_of_nodes() == 0:
logging.error("Görselleştirilecek geçerli veya boş olmayan bir graf bulunamadı.")
return None
# Ağ analizi sonuçlarını yükle
logging.info(f"Ağ analizi sonuçları '{analysis_filename}.parquet' dosyasından yükleniyor...")
analysis_df = storage.load_dataframe(analysis_filename, []) # Sütunları bilmediğimiz için boş liste
metrics_dict = {}
min_size_val, max_size_val = 0, 1 # Boyut ölçekleme için min/max
if analysis_df is not None and not analysis_df.empty and 'concept_id' in analysis_df.columns:
# Eksik metrik sütunlarını kontrol et ve ekle (NaN ile)
required_metrics = [size_metric, color_metric]
for metric in required_metrics:
if metric not in analysis_df.columns:
logging.warning(f"Analiz sonuçlarında '{metric}' sütunu bulunamadı. Varsayılan değerler kullanılacak.")
analysis_df[metric] = None
# Boyut metriği için min/max değerleri bul (NaN olmayanlardan)
if size_metric in analysis_df.columns and analysis_df[size_metric].notna().any():
min_size_val = analysis_df[size_metric].min()
max_size_val = analysis_df[size_metric].max()
# Kolay erişim için sözlüğe çevir
metrics_dict = analysis_df.set_index('concept_id').to_dict('index')
logging.info("Ağ analizi metrikleri yüklendi.")
else:
logging.warning("Ağ analizi sonuçları yüklenemedi veya boş. Varsayılan düğüm boyutları/renkleri kullanılacak.")
logging.info(f"'{output_filename}' için Pyvis ağı oluşturuluyor...")
net = Network(notebook=False, height=height, width=width, heading='ChronoSense Konsept Ağı (Metriklerle)', cdn_resources='remote')
net.barnes_hut(gravity=-8000, central_gravity=0.1, spring_length=150, spring_strength=0.005, damping=0.09)
# Düğümleri (Nodes) Pyvis'e ekle (Boyut ve Renk ile)
for node, attrs in graph.nodes(data=True):
node_label = attrs.get('name', str(node))
node_metrics = metrics_dict.get(node, {}) # Bu düğüm için metrikleri al, yoksa boş dict
# Boyutu hesapla
size_val = node_metrics.get(size_metric)
node_size = scale_value(size_val, min_size_val, max_size_val, new_min=10, new_max=40) # 10-40 arası boyut
# Rengi hesapla
color_val = node_metrics.get(color_metric)
node_color = get_color_for_community(color_val)
# Başlığı (Title) güncelle (metrikleri ekle)
node_title = f"ID: {node}<br>Name: {attrs.get('name', 'N/A')}"
node_title += f"<br>{size_metric}: {size_val:.3f}" if pd.notna(size_val) else ""
node_title += f"<br>{color_metric}: {int(color_val)}" if pd.notna(color_val) else ""
net.add_node(node, label=node_label, title=node_title, size=node_size, color=node_color)
# Kenarları (Edges) Pyvis'e ekle (Öncekiyle aynı, sadece renk/kalınlık ayarları biraz daha belirgin)
for source, target, attrs in graph.edges(data=True):
edge_title = f"Type: {attrs.get('type', 'N/A')}"
edge_value = 0.5 ; edge_color = "#DDDDDD" # Daha soluk varsayılan
edge_type = attrs.get('type')
weight = attrs.get('weight', 0)
if edge_type == 'extracted':
edge_title += f"<br>Relation: {attrs.get('relation_type', 'N/A')}"
edge_value = max(0.6, weight) # extracted ilişkiler biraz daha belirgin olsun
edge_color = "#FF6347" # Koyu turuncu/kırmızımsı
elif edge_type == 'similarity':
sim_score = attrs.get('similarity', weight)
edge_title += f"<br>Similarity: {sim_score:.3f}"
edge_value = sim_score # Benzerlikle orantılı
edge_color = "#4682B4" # Çelik mavisi
elif edge_type == 'combined':
edge_title += f"<br>Relation: {attrs.get('relation_type', 'N/A')}"
sim_score = attrs.get('similarity', weight)
edge_title += f"<br>Similarity: {sim_score:.3f}"
edge_value = max(0.6, sim_score) # Combined da belirgin olsun
edge_color = "#9370DB" # Orta mor
net.add_edge(source, target, title=edge_title, value=max(0.1, edge_value), color=edge_color)
if show_buttons:
net.show_buttons(filter_=['physics', 'nodes', 'edges'])
try:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
output_path = OUTPUT_DIR / output_filename
net.save_graph(str(output_path))
logging.info(f"Ağ görselleştirmesi başarıyla '{output_path}' olarak kaydedildi.")
return str(output_path)
except Exception as e:
logging.exception(f"Ağ görselleştirmesi kaydedilirken hata oluştu: {e}")
return None |