Spaces:
Sleeping
Sleeping
| """ | |
| Graph Visualization Utilities | |
| ============================== | |
| Functions for rendering knowledge graphs as interactive HTML (PyVis) | |
| and statistical charts (Plotly). | |
| """ | |
| from typing import Dict, Any | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from plotly.subplots import make_subplots | |
| from src.graph_builder import KnowledgeGraph, ENTITY_COLORS | |
| # ------------------------------------------------------------------ | |
| # PyVis interactive graph | |
| # ------------------------------------------------------------------ | |
| def create_pyvis_graph(kg: KnowledgeGraph, height: str = "650px") -> str: | |
| """ | |
| Render *kg* as an interactive PyVis graph and return raw HTML. | |
| The HTML string can be embedded directly with | |
| ``streamlit.components.v1.html()``. | |
| """ | |
| net = kg.to_pyvis(height=height) | |
| # Generate HTML string (PyVis >= 0.3 supports generate_html) | |
| try: | |
| html = net.generate_html() | |
| except AttributeError: | |
| # Fallback for older pyvis | |
| import tempfile, os | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".html", mode="w", encoding="utf-8") | |
| net.save_graph(tmp.name) | |
| tmp.close() | |
| with open(tmp.name, "r", encoding="utf-8") as f: | |
| html = f.read() | |
| os.unlink(tmp.name) | |
| return html | |
| # ------------------------------------------------------------------ | |
| # Plotly statistical charts | |
| # ------------------------------------------------------------------ | |
| _CHART_LAYOUT = dict( | |
| paper_bgcolor="#0a0a0a", | |
| plot_bgcolor="#111111", | |
| font_color="white", | |
| font_size=13, | |
| margin=dict(l=40, r=40, t=50, b=40), | |
| ) | |
| def graph_stats_chart(stats: Dict[str, Any]) -> go.Figure: | |
| """ | |
| Create a combined Plotly figure with: | |
| - Entity type distribution (bar) | |
| - Relationship type distribution (bar) | |
| """ | |
| entity_counts = stats.get("entity_type_counts", {}) | |
| relation_counts = stats.get("relation_type_counts", {}) | |
| fig = make_subplots( | |
| rows=1, | |
| cols=2, | |
| subplot_titles=("Entity Types", "Relationship Types"), | |
| horizontal_spacing=0.15, | |
| ) | |
| # --- Entity type bar chart --- | |
| if entity_counts: | |
| types = list(entity_counts.keys()) | |
| counts = list(entity_counts.values()) | |
| colors = [ENTITY_COLORS.get(t, "#888888") for t in types] | |
| fig.add_trace( | |
| go.Bar( | |
| x=types, | |
| y=counts, | |
| marker_color=colors, | |
| text=counts, | |
| textposition="outside", | |
| name="Entity Types", | |
| showlegend=False, | |
| ), | |
| row=1, | |
| col=1, | |
| ) | |
| # --- Relationship type bar chart --- | |
| if relation_counts: | |
| rels = list(relation_counts.keys()) | |
| rcounts = list(relation_counts.values()) | |
| fig.add_trace( | |
| go.Bar( | |
| x=rels, | |
| y=rcounts, | |
| marker_color="#00d4ff", | |
| text=rcounts, | |
| textposition="outside", | |
| name="Relationships", | |
| showlegend=False, | |
| ), | |
| row=1, | |
| col=2, | |
| ) | |
| fig.update_layout( | |
| height=370, | |
| **_CHART_LAYOUT, | |
| ) | |
| fig.update_xaxes(tickangle=-40) | |
| return fig | |
| def centrality_chart(top_nodes: list) -> go.Figure: | |
| """ | |
| Horizontal bar chart of the top-N most central nodes. | |
| """ | |
| if not top_nodes: | |
| fig = go.Figure() | |
| fig.update_layout( | |
| title="No nodes to display", | |
| **_CHART_LAYOUT, | |
| height=300, | |
| ) | |
| return fig | |
| names = [n[0] for n in reversed(top_nodes)] | |
| values = [round(n[1], 4) for n in reversed(top_nodes)] | |
| fig = go.Figure( | |
| go.Bar( | |
| x=values, | |
| y=names, | |
| orientation="h", | |
| marker=dict( | |
| color=values, | |
| colorscale=[[0, "#0a0a0a"], [0.5, "#00d4ff"], [1, "#00ff88"]], | |
| ), | |
| text=[f"{v:.3f}" for v in values], | |
| textposition="outside", | |
| ) | |
| ) | |
| fig.update_layout( | |
| title="Top Nodes by Degree Centrality", | |
| xaxis_title="Centrality Score", | |
| height=max(300, len(top_nodes) * 35 + 100), | |
| **_CHART_LAYOUT, | |
| ) | |
| return fig | |
| def community_chart(communities: list) -> go.Figure: | |
| """Pie chart showing community sizes.""" | |
| if not communities: | |
| fig = go.Figure() | |
| fig.update_layout(title="No communities detected", **_CHART_LAYOUT, height=300) | |
| return fig | |
| labels = [f"Community {i+1}" for i in range(len(communities))] | |
| sizes = [len(c) for c in communities] | |
| fig = go.Figure( | |
| go.Pie( | |
| labels=labels, | |
| values=sizes, | |
| hole=0.45, | |
| marker=dict( | |
| colors=["#00ff88", "#00d4ff", "#a855f7", "#f59e0b", "#ec4899", | |
| "#6366f1", "#14b8a6", "#f43f5e", "#84cc16", "#06b6d4"], | |
| ), | |
| textinfo="label+percent", | |
| textfont_size=12, | |
| ) | |
| ) | |
| fig.update_layout( | |
| title="Community Distribution", | |
| height=370, | |
| **_CHART_LAYOUT, | |
| ) | |
| return fig | |