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