Source code for class_factory.concept_web.visualize_graph


"""
This module provides functions to visualize a concept map generated from processed relationships between concepts.
It includes functionalities to create interactive graph visualizations and generate word clouds representing the concepts.

The primary functionalities include:
1. **Interactive Graph Visualization**: Converts a NetworkX graph into an interactive HTML visualization using pyvis.
   The graph can be manipulated dynamically in a web browser, allowing for physics simulations, node filtering, and clustering.
2. **Word Cloud Generation**: Creates a word cloud image from a list of concepts, visually representing the frequency
   of each concept.

Main Functions:
- `visualize_graph_interactive(G: nx.Graph, output_path: Union[Path, str]) -> None`:
    Visualizes the given graph interactively using pyvis and saves the result as an HTML file. The nodes are colored
    based on their community, and the visualization allows for interactive exploration of the graph.

Workflow:
1. **Graph Conversion**: Converts the provided NetworkX graph into a pyvis graph, applying styles and attributes
   like node size and edge width based on centrality and relationship frequency.
2. **Interactive Visualization**: Saves the interactive graph as an HTML file, which can be explored in any web browser.

Dependencies:
- NetworkX: For graph data structure and manipulation.
- Matplotlib: For color mapping and displaying the word cloud.
- Pyvis: For creating interactive graph visualizations in HTML.
"""

from pathlib import Path
from typing import List, Union

import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import networkx as nx
from pyvis.network import Network


[docs] def visualize_graph_interactive( G: nx.Graph, output_path: Union[Path, str], directed: bool = False, dark_mode: bool = True, max_nodes: int = 250, centrality_method: str = "degree", expand_neighbors: bool = True, ) -> None: """ Create an interactive HTML visualization of a concept map using pyvis. Args: G (nx.Graph): The graph to visualize (with community and text_size attributes). output_path (Union[Path, str]): Path to save the HTML file. directed (bool, optional): If True, show edge arrows. Defaults to False. dark_mode (bool, optional): Use dark background. Defaults to True. """ # Filter large graphs if max_nodes is not None and G.number_of_nodes() > max_nodes: G = filter_graph_by_centrality( G, max_nodes=max_nodes, method=centrality_method, expand_neighbors=expand_neighbors, ) if dark_mode: net = Network(height='750px', width='100%', bgcolor='#222222', font_color='white', directed=directed) else: net = Network(height='750px', width='100%', bgcolor='white', font_color='black', directed=directed) # Generate a color map based on the number of communities communities = set(nx.get_node_attributes(G, 'community').values()) color_map = plt.colormaps['tab20'] # 'tab20' is a colormap with 20 distinct colors community_colors = {community: mcolors.to_hex(color_map(i)) for i, community in enumerate(communities)} # Assign colors to nodes based on their community group for node in G.nodes(): community = G.nodes[node]['community'] G.nodes[node]['color'] = community_colors[community] # Set the color attribute # Convert the NetworkX graph to a pyvis graph and add text size net.from_nx(G) for node in net.nodes: node["size"] = node.get('text_size', 20) node["font"].update({"size": node.get('text_size', 20)}) for edge in net.edges: edge['relation'] = list(edge['relation']) edge['title'] = ", ".join(edge['relation']) edge['width'] = edge.get('normalized_weight', edge.get('weight', 1)) if directed: edge["arrows"] = "to" # Add physics controls for a dynamic layout net.show_buttons(filter_=['layout', 'physics']) output_path = str(output_path) net.save_graph(output_path) print(f"Concept map saved to {output_path}")
# Optionally, you can also open it directly in a browser # net.show(output_path)
[docs] def filter_graph_by_centrality( G: nx.Graph, max_nodes: int = 250, method: str = "pagerank", expand_neighbors: bool = True, ) -> nx.Graph: """ Return an induced subgraph containing up to `max_nodes` most-central nodes. Parameters: G: original NetworkX graph max_nodes: desired maximum number of nodes in the returned graph method: centrality method to rank nodes ('pagerank', 'degree', 'betweenness') expand_neighbors: if True, after selecting top central nodes, try to include their 1-hop neighbors to preserve local context until max_nodes reached. Returns: A copy of the induced subgraph with selected nodes. """ if max_nodes is None or G.number_of_nodes() <= max_nodes: return G.copy() method = method.lower() if method == "pagerank": try: centrality = nx.pagerank(G) except Exception: centrality = dict(G.degree()) elif method == "degree": centrality = nx.degree_centrality(G) elif method == "betweenness": centrality = nx.betweenness_centrality(G) else: raise ValueError(f"Unsupported centrality method: {method}") # Sort nodes by centrality descending sorted_nodes = sorted(centrality.items(), key=lambda kv: kv[1], reverse=True) selected = [n for n, _ in sorted_nodes[:max_nodes]] selected_set = set(selected) # Optionally expand to include neighbors to preserve connectivity/context if expand_neighbors: for n, _ in sorted_nodes[: max_nodes * 2]: if len(selected_set) >= max_nodes: break for nbr in G.neighbors(n): if len(selected_set) >= max_nodes: break selected_set.add(nbr) selected = list(selected_set)[:max_nodes] return G.subgraph(selected).copy()
if __name__ == "__main__": import json import os from pathlib import Path # env setup from dotenv import load_dotenv load_dotenv() from class_factory.concept_web.build_concept_map import ( build_graph, detect_communities) # Path definitions projectDir = Path.home() / Path(os.getenv('projectDir')) dataDir = projectDir / "data/" with open(dataDir / 'interim/conceptlist_test.json', 'r') as f: concept_list = json.load(f) with open(dataDir / 'interim/relationship_list_test.json', 'r') as f: relationship_list = json.load(f) # Create and save the interactive graph as an HTML file output_path = str(dataDir / "interim/interactive_concept_map_test.html") # Build the graph G_base = build_graph(relationship_list) # Detect communities using Louvain method G = detect_communities(G_base, method="leiden") visualize_graph_interactive(G, output_path, dark_mode=False, max_nodes=500)