|
|
|
|
| import networkx as nx
|
| from pyvis.network import Network
|
| import logging
|
| from pathlib import Path
|
| import pandas as pd
|
| import random
|
|
|
|
|
| from src.data_management import storage
|
|
|
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
|
| OUTPUT_DIR = Path("output/graphs")
|
| DEFAULT_GRAPH_FILENAME = "concept_network"
|
|
|
| DEFAULT_ANALYSIS_FILENAME = storage.NETWORK_ANALYSIS_FILENAME
|
|
|
|
|
|
|
|
|
|
|
| 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):
|
| return "#CCCCCC"
|
| return colors[int(community_id) % len(colors)]
|
|
|
| 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):
|
| return new_min
|
|
|
| scaled = ((value - min_val) / (max_val - min_val)) * (new_max - new_min) + new_min
|
| return max(new_min, min(scaled, new_max))
|
|
|
|
|
| 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',
|
| color_metric: str = 'community_id',
|
| 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
|
|
|
|
|
| logging.info(f"Ağ analizi sonuçları '{analysis_filename}.parquet' dosyasından yükleniyor...")
|
| analysis_df = storage.load_dataframe(analysis_filename, [])
|
| metrics_dict = {}
|
| min_size_val, max_size_val = 0, 1
|
|
|
| if analysis_df is not None and not analysis_df.empty and 'concept_id' in analysis_df.columns:
|
|
|
| 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
|
|
|
|
|
| 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()
|
|
|
|
|
| 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)
|
|
|
|
|
| for node, attrs in graph.nodes(data=True):
|
| node_label = attrs.get('name', str(node))
|
| node_metrics = metrics_dict.get(node, {})
|
|
|
|
|
| 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)
|
|
|
|
|
| color_val = node_metrics.get(color_metric)
|
| node_color = get_color_for_community(color_val)
|
|
|
|
|
| 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)
|
|
|
|
|
| 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"
|
|
|
| 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)
|
| edge_color = "#FF6347"
|
| elif edge_type == 'similarity':
|
| sim_score = attrs.get('similarity', weight)
|
| edge_title += f"<br>Similarity: {sim_score:.3f}"
|
| edge_value = sim_score
|
| edge_color = "#4682B4"
|
| 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)
|
| edge_color = "#9370DB"
|
|
|
| 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 |