CoolFace
Apppublic

CultriX/Generate-Knowledge-Graphs

sourceHugging Facemitupdated 1y agoView on Hugging Face
3likes
visualizer.py498 linesDownload Raw Back to src
1import matplotlib2matplotlib.use('Agg')  # Use non-interactive backend to avoid GUI issues3import matplotlib.pyplot as plt4import networkx as nx5import numpy as np6from typing import Dict, List, Any, Tuple, Optional7import json8import io9import base6410import tempfile11import os12import plotly.graph_objects as go13import plotly.express as px14from pyvis.network import Network15 16class GraphVisualizer:17    def __init__(self):18        self.color_map = {19            'PERSON': '#FF6B6B',20            'ORGANIZATION': '#4ECDC4', 21            'LOCATION': '#45B7D1',22            'CONCEPT': '#96CEB4',23            'EVENT': '#FFEAA7',24            'OBJECT': '#DDA0DD',25            'UNKNOWN': '#95A5A6'26        }27        28    def visualize_graph(self, 29                       graph: nx.DiGraph, 30                       layout_type: str = "spring",31                       show_labels: bool = True,32                       show_edge_labels: bool = False,33                       node_size_factor: float = 1.0,34                       figsize: Tuple[int, int] = (12, 8)) -> str:35        """Create a matplotlib visualization of the graph and return file path."""36        37        if not graph.nodes():38            return self._create_empty_graph_image()39        40        # Create figure41        plt.figure(figsize=figsize)42        plt.clf()43        44        # Calculate layout45        pos = self._calculate_layout(graph, layout_type)46        47        # Get node properties48        node_colors = [self.color_map.get(graph.nodes[node].get('type', 'UNKNOWN'), '#95A5A6') 49                      for node in graph.nodes()]50        node_sizes = [graph.nodes[node].get('size', 20) * node_size_factor * 10 51                     for node in graph.nodes()]52        53        # Draw nodes54        nx.draw_networkx_nodes(graph, pos, 55                              node_color=node_colors,56                              node_size=node_sizes,57                              alpha=0.8)58        59        # Draw edges60        nx.draw_networkx_edges(graph, pos,61                              edge_color='gray',62                              arrows=True,63                              arrowsize=20,64                              alpha=0.6,65                              width=1.5)66        67        # Draw labels68        if show_labels:69            # Create labels with importance scores70            labels = {}71            for node in graph.nodes():72                importance = graph.nodes[node].get('importance', 0.0)73                labels[node] = f"{node}\n({importance:.2f})"74            75            nx.draw_networkx_labels(graph, pos, labels, font_size=8)76        77        # Draw edge labels78        if show_edge_labels:79            edge_labels = {(u, v): data.get('relationship', '') 80                          for u, v, data in graph.edges(data=True)}81            nx.draw_networkx_edge_labels(graph, pos, edge_labels, font_size=6)82        83        plt.title("Knowledge Graph", fontsize=16, fontweight='bold')84        plt.axis('off')85        plt.tight_layout()86        87        # Save to temporary file88        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')89        plt.savefig(temp_file.name, format='png', dpi=150, bbox_inches='tight')90        plt.close()91        92        return temp_file.name93    94    def _calculate_layout(self, graph: nx.DiGraph, layout_type: str) -> Dict[str, Tuple[float, float]]:95        """Calculate node positions using specified layout algorithm."""96        try:97            if layout_type == "spring":98                return nx.spring_layout(graph, k=1, iterations=50)99            elif layout_type == "circular":100                return nx.circular_layout(graph)101            elif layout_type == "shell":102                return nx.shell_layout(graph)103            elif layout_type == "kamada_kawai":104                return nx.kamada_kawai_layout(graph)105            elif layout_type == "random":106                return nx.random_layout(graph)107            else:108                return nx.spring_layout(graph, k=1, iterations=50)109        except:110            # Fallback to simple layout if algorithm fails111            return nx.spring_layout(graph, k=1, iterations=50)112    113    def _create_empty_graph_image(self) -> str:114        """Create an image for empty graph."""115        plt.figure(figsize=(8, 6))116        plt.text(0.5, 0.5, 'No graph data to display', 117                horizontalalignment='center', verticalalignment='center',118                fontsize=16, transform=plt.gca().transAxes)119        plt.axis('off')120        121        # Save to temporary file122        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')123        plt.savefig(temp_file.name, format='png', dpi=150, bbox_inches='tight')124        plt.close()125        126        return temp_file.name127    128    def create_interactive_html(self, graph: nx.DiGraph) -> str:129        """Create an interactive HTML visualization using vis.js."""130        if not graph.nodes():131            return "<div>No graph data to display</div>"132        133        # Convert graph to vis.js format134        nodes = []135        edges = []136        137        for node, data in graph.nodes(data=True):138            nodes.append({139                "id": node,140                "label": node,141                "color": self.color_map.get(data.get('type', 'UNKNOWN'), '#95A5A6'),142                "size": data.get('size', 20),143                "title": f"Type: {data.get('type', 'UNKNOWN')}<br>"144                        f"Importance: {data.get('importance', 0.0):.2f}<br>"145                        f"Description: {data.get('description', 'N/A')}"146            })147        148        for u, v, data in graph.edges(data=True):149            edges.append({150                "from": u,151                "to": v,152                "label": data.get('relationship', ''),153                "title": data.get('description', ''),154                "arrows": {"to": {"enabled": True}}155            })156        157        html_template = f"""158        <!DOCTYPE html>159        <html>160        <head>161            <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>162            <style>163                #mynetworkid {{164                    width: 100%;165                    height: 600px;166                    border: 1px solid lightgray;167                }}168            </style>169        </head>170        <body>171            <div id="mynetworkid"></div>172            173            <script>174                var nodes = new vis.DataSet({json.dumps(nodes)});175                var edges = new vis.DataSet({json.dumps(edges)});176                var container = document.getElementById('mynetworkid');177                178                var data = {{179                    nodes: nodes,180                    edges: edges181                }};182                183                var options = {{184                    nodes: {{185                        shape: 'dot',186                        scaling: {{187                            min: 10,188                            max: 30189                        }},190                        font: {{191                            size: 12,192                            face: 'Tahoma'193                        }}194                    }},195                    edges: {{196                        font: {{align: 'middle'}},197                        color: {{color:'gray'}},198                        arrows: {{to: {{enabled: true, scaleFactor: 1}}}}199                    }},200                    physics: {{201                        enabled: true,202                        stabilization: {{enabled: true, iterations: 200}}203                    }},204                    interaction: {{205                        hover: true,206                        tooltipDelay: 200207                    }}208                }};209                210                var network = new vis.Network(container, data, options);211            </script>212        </body>213        </html>214        """215        216        return html_template217    218    def create_statistics_summary(self, graph: nx.DiGraph, stats: Dict[str, Any]) -> str:219        """Create a formatted statistics summary."""220        if not graph.nodes():221            return "No graph statistics available."222        223        # Entity type distribution224        type_counts = {}225        for node, data in graph.nodes(data=True):226            node_type = data.get('type', 'UNKNOWN')227            type_counts[node_type] = type_counts.get(node_type, 0) + 1228        229        # Relationship type distribution230        rel_counts = {}231        for u, v, data in graph.edges(data=True):232            rel_type = data.get('relationship', 'unknown')233            rel_counts[rel_type] = rel_counts.get(rel_type, 0) + 1234        235        summary = f"""236        ## Graph Statistics237        238        **Basic Metrics:**239        - Nodes: {stats['num_nodes']}240        - Edges: {stats['num_edges']}241        - Density: {stats['density']:.3f}242        - Connected: {'Yes' if stats['is_connected'] else 'No'}243        - Components: {stats['num_components']}244        - Average Degree: {stats['avg_degree']:.2f}245        246        **Entity Types:**247        """248        249        for entity_type, count in sorted(type_counts.items()):250            summary += f"\n- {entity_type}: {count}"251        252        summary += "\n\n**Relationship Types:**"253        for rel_type, count in sorted(rel_counts.items()):254            summary += f"\n- {rel_type}: {count}"255        256        return summary257    258    def create_entity_list(self, graph: nx.DiGraph, sort_by: str = "importance") -> str:259        """Create a formatted list of entities."""260        if not graph.nodes():261            return "No entities found."262        263        entities = []264        for node, data in graph.nodes(data=True):265            entities.append({266                'name': node,267                'type': data.get('type', 'UNKNOWN'),268                'importance': data.get('importance', 0.0),269                'description': data.get('description', 'N/A'),270                'connections': graph.degree(node)271            })272        273        # Sort entities274        if sort_by == "importance":275            entities.sort(key=lambda x: x['importance'], reverse=True)276        elif sort_by == "connections":277            entities.sort(key=lambda x: x['connections'], reverse=True)278        elif sort_by == "name":279            entities.sort(key=lambda x: x['name'])280        281        entity_list = "## Entities\n\n"282        for entity in entities:283            entity_list += f"""284**{entity['name']}** ({entity['type']})285- Importance: {entity['importance']:.2f}286- Connections: {entity['connections']}287- Description: {entity['description']}288 289"""290        291        return entity_list292    293    def get_layout_options(self) -> List[str]:294        """Get available layout options."""295        return ["spring", "circular", "shell", "kamada_kawai", "random"]296    297    def get_entity_types(self, graph: nx.DiGraph) -> List[str]:298        """Get unique entity types from the graph."""299        types = set()300        for node, data in graph.nodes(data=True):301            types.add(data.get('type', 'UNKNOWN'))302        return sorted(list(types))303    304    def create_plotly_interactive(self, graph: nx.DiGraph, layout_type: str = "spring") -> go.Figure:305        """Create an interactive Plotly visualization of the graph."""306        if not graph.nodes():307            # Return empty figure308            fig = go.Figure()309            fig.add_annotation(310                text="No graph data to display",311                xref="paper", yref="paper",312                x=0.5, y=0.5, xanchor='center', yanchor='middle',313                showarrow=False, font=dict(size=16)314            )315            return fig316        317        # Calculate layout318        pos = self._calculate_layout(graph, layout_type)319        320        # Prepare node data321        node_x = []322        node_y = []323        node_text = []324        node_info = []325        node_colors = []326        node_sizes = []327        328        for node in graph.nodes():329            x, y = pos[node]330            node_x.append(x)331            node_y.append(y)332            333            data = graph.nodes[node]334            node_type = data.get('type', 'UNKNOWN')335            importance = data.get('importance', 0.0)336            description = data.get('description', 'N/A')337            connections = graph.degree(node)338            339            node_text.append(node)340            node_info.append(341                f"<b>{node}</b><br>"342                f"Type: {node_type}<br>"343                f"Importance: {importance:.2f}<br>"344                f"Connections: {connections}<br>"345                f"Description: {description}"346            )347            node_colors.append(self.color_map.get(node_type, '#95A5A6'))348            node_sizes.append(max(10, data.get('size', 20)))349        350        # Prepare edge data351        edge_x = []352        edge_y = []353        edge_info = []354        355        for edge in graph.edges():356            x0, y0 = pos[edge[0]]357            x1, y1 = pos[edge[1]]358            edge_x.extend([x0, x1, None])359            edge_y.extend([y0, y1, None])360            361            edge_data = graph.edges[edge]362            relationship = edge_data.get('relationship', 'connected')363            edge_info.append(f"{edge[0]} → {edge[1]}<br>Relationship: {relationship}")364        365        # Create edge trace366        edge_trace = go.Scatter(367            x=edge_x, y=edge_y,368            line=dict(width=2, color='gray'),369            hoverinfo='none',370            mode='lines'371        )372        373        # Create node trace374        node_trace = go.Scatter(375            x=node_x, y=node_y,376            mode='markers+text',377            hoverinfo='text',378            text=node_text,379            hovertext=node_info,380            textposition="middle center",381            marker=dict(382                size=node_sizes,383                color=node_colors,384                line=dict(width=2, color='white')385            )386        )387        388        # Create figure389        fig = go.Figure(data=[edge_trace, node_trace],390                       layout=go.Layout(391                           title='Interactive Knowledge Graph',392                           titlefont_size=16,393                           showlegend=False,394                           hovermode='closest',395                           margin=dict(b=20,l=5,r=5,t=40),396                           annotations=[ dict(397                               text="Hover over nodes for details. Drag to pan, scroll to zoom.",398                               showarrow=False,399                               xref="paper", yref="paper",400                               x=0.005, y=-0.002,401                               xanchor='left', yanchor='bottom',402                               font=dict(color="gray", size=12)403                           )],404                           xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),405                           yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),406                           plot_bgcolor='white'407                       ))408        409        return fig410    411    def create_pyvis_interactive(self, graph: nx.DiGraph, layout_type: str = "spring") -> str:412        """Create an interactive pyvis visualization and return HTML file path."""413        if not graph.nodes():414            return self._create_empty_pyvis_graph()415        416        # Create pyvis network417        net = Network(height="600px", width="100%", bgcolor="#ffffff", font_color="black")418        419        # Configure physics420        net.set_options("""421        {422          "physics": {423            "enabled": true,424            "stabilization": {"enabled": true, "iterations": 200},425            "barnesHut": {426              "gravitationalConstant": -2000,427              "centralGravity": 0.3,428              "springLength": 95,429              "springConstant": 0.04,430              "damping": 0.09431            }432          },433          "interaction": {434            "hover": true,435            "tooltipDelay": 200,436            "hideEdgesOnDrag": false437          }438        }439        """)440        441        # Add nodes442        for node, data in graph.nodes(data=True):443            node_type = data.get('type', 'UNKNOWN')444            importance = data.get('importance', 0.0)445            description = data.get('description', 'N/A')446            connections = graph.degree(node)447            448            # Node properties449            color = self.color_map.get(node_type, '#95A5A6')450            size = max(10, data.get('size', 20))451            452            # Tooltip text453            title = f"""454            <b>{node}</b><br>455            Type: {node_type}<br>456            Importance: {importance:.2f}<br>457            Connections: {connections}<br>458            Description: {description}459            """460            461            net.add_node(node, label=node, title=title, color=color, size=size)462        463        # Add edges464        for u, v, data in graph.edges(data=True):465            relationship = data.get('relationship', 'connected')466            title = f"{u} → {v}<br>Relationship: {relationship}"467            468            net.add_edge(u, v, title=title, arrows="to", color="gray")469        470        # Save to temporary file471        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.html', mode='w')472        net.save_graph(temp_file.name)473        temp_file.close()474        475        return temp_file.name476    477    def _create_empty_pyvis_graph(self) -> str:478        """Create an empty pyvis graph."""479        net = Network(height="600px", width="100%", bgcolor="#ffffff", font_color="black")480        net.add_node(1, label="No graph data", color="#cccccc")481        482        temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.html', mode='w')483        net.save_graph(temp_file.name)484        temp_file.close()485        486        return temp_file.name487    488    def get_visualization_options(self) -> List[str]:489        """Get available visualization types."""490        return ["matplotlib", "plotly", "pyvis", "vis.js"]491    492    def get_relationship_types(self, graph: nx.DiGraph) -> List[str]:493        """Get unique relationship types from the graph."""494        types = set()495        for u, v, data in graph.edges(data=True):496            types.add(data.get('relationship', 'unknown'))497        return sorted(list(types))498