| |
|
| |
|
| | 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 |