VERSION = "1.1 6-Jan-2025"
print(f"Pink Panther Graphs v{VERSION}")
"""

1. Download Miniconda at https://docs.conda.io/en/latest/miniconda.html and install it.

2. Create Python environment

#conda deactivate
#conda env list
#conda env remove --name allkinds
conda create --name allkinds python=3.10
conda activate allkinds
pip install networkx matplotlib rdflib

3. How To Launch On Laptop

cd '/Users/cshallah/OraPub/0-Blog-Wider-View/HTML-new/20250101 A Graphs/projects/graphs-all-kinds'
conda activate allkinds
python pink_panther_graphs.py

"""


import networkx as nx
import matplotlib.pyplot as plt
from rdflib import Graph, Literal, RDF, URIRef, Namespace
import json
import matplotlib.patches as mpatches

class PinkPantherGraphs:
    def __init__(self):
        self.fig_size = (12, 8)
        
    def create_network_graph(self):
        """
        Creates a simple network graph of the Pink Panther heist
        """
        print("\n=== Network Graph Example ===")
        print("Demonstrating simple connections between entities in the heist")
        
        G = nx.DiGraph()
        
        # Add nodes by category
        suspects = ['The Phantom', 'Inside Man']
        locations = ['Casino Royale', 'Safe Room', 'Hotel Lobby']
        investigators = ['Inspector Clouseau', 'Security Chief']
        
        # Add nodes with different colors for each category
        for suspect in suspects:
            G.add_node(suspect, node_type='suspect')
        for location in locations:
            G.add_node(location, node_type='location')
        for investigator in investigators:
            G.add_node(investigator, node_type='investigator')
            
        # Add edges (connections)
        edges = [
            ('The Phantom', 'Casino Royale'),
            ('The Phantom', 'Inside Man'),
            ('Inside Man', 'Safe Room'),
            ('Inspector Clouseau', 'Casino Royale'),
            ('Security Chief', 'Safe Room'),
            ('Casino Royale', 'Safe Room'),
            ('Hotel Lobby', 'Casino Royale')
        ]
        G.add_edges_from(edges)

        # Print network graph information
        print("\nNetwork Graph Data:")
        print("\nNodes by Category:")
        print("  Suspects:", suspects)
        print("  Locations:", locations)
        print("  Investigators:", investigators)
        
        print("\nConnections (Edges):")
        for edge in edges:
            print(f"  {edge[0]} --> {edge[1]}")
                
        # Visualize the graph
        plt.figure(figsize=self.fig_size)
        pos = nx.spring_layout(G, seed=1)
        
        # Draw nodes with different colors
        node_colors = ['red' if G.nodes[node]['node_type'] == 'suspect' 
                      else 'lightblue' if G.nodes[node]['node_type'] == 'location'
                      else 'green' for node in G.nodes()]
        
        # Draw nodes and edges separately for more control
        nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=2000)
        nx.draw_networkx_edges(G, pos, 
                             arrows=True, 
                             arrowsize=30,
                             edge_color='black',
                             width=2,
                             connectionstyle='arc3,rad=0.2',  # Add curved connections
                             node_size=2000)  # Tell edge drawing about node size
        
        # Draw labels below nodes by adjusting the vertical position
        label_pos = {node: (coords[0], coords[1] - 0.16) for node, coords in pos.items()}
        nx.draw_networkx_labels(G, label_pos, font_size=10, font_weight='bold')
        
        plt.title("Pink Panther Heist - Network Graph")
        
        # Add legend
        legend_elements = [
            mpatches.Patch(color='red', label='Suspect'),
            mpatches.Patch(color='lightblue', label='Location'),
            mpatches.Patch(color='green', label='Investigator')
        ]
        plt.legend(handles=legend_elements)
        
        plt.axis('off')
        plt.show()
        
        
    def create_property_graph(self):
        """
        Creates and visualizes a property graph representation of the Pink Panther heist
        """
        print("\n=== Property Graph Example ===")
        print("Demonstrating relationships with properties")
        
        # Define nodes with properties
        nodes = {
            'phantom': {
                'id': 'phantom',
                'type': 'person',
                'name': 'The Phantom',
                'properties': {
                    'expertise': ['safe_cracking', 'disguise'],
                    'last_known_location': 'Monaco'
                }
            },
            'casino': {
                'id': 'casino',
                'type': 'location',
                'name': 'Casino Royale',
                'properties': {
                    'security_level': 'Maximum',
                    'surveillance_coverage': '90%'
                }
            },
            'diamond': {
                'id': 'diamond',
                'type': 'item',
                'name': 'Pink Panther Diamond',
                'properties': {
                    'value': '50 million',
                    'security': 'Level 10'
                }
            }
        }
        
        # Define relationships with properties
        relationships = [
            {
                'from': 'phantom',
                'to': 'casino',
                'type': 'VISITED',
                'properties': {
                    'timestamp': '2024-03-15 23:00',
                    'duration': '45min',
                    'detected': False
                }
            },
            {
                'from': 'phantom',
                'to': 'diamond',
                'type': 'STOLE',
                'properties': {
                    'timestamp': '2024-03-15 23:30',
                    'method': 'Safe cracking'
                }
            }
        ]
        
        # Print property graph information
        print("\nNodes with Properties:")
        print(json.dumps(nodes, indent=2))
        print("\nRelationships with Properties:")
        print(json.dumps(relationships, indent=2))

        # Create visualization graph
        G = nx.DiGraph()
        
        # Add nodes to visualization graph
        for node_id, node_data in nodes.items():
            label = f"{node_data['name']}\n({', '.join(f'{k}: {v}' for k, v in node_data['properties'].items())})"
            G.add_node(node_id, node_type=node_data['type'], label=label)
        
        # Add edges to visualization graph
        for rel in relationships:
            label = f"{rel['type']}\n({', '.join(f'{k}: {v}' for k, v in rel['properties'].items())})"
            G.add_edge(rel['from'], rel['to'], relationship=rel['type'], label=label)
        
        # Visualize the property graph
        plt.figure(figsize=self.fig_size)
        
        # Create layout with more horizontal spread
        pos = nx.spring_layout(G, 
                             k=0.5,      # Reduced from 2 to bring nodes closer
                             iterations=200,
                             seed=18)
        
        # Draw nodes
        node_colors = ['lightcoral' if G.nodes[node]['node_type'] == 'person'
                      else 'lightblue' if G.nodes[node]['node_type'] == 'location'
                      else 'lightgreen' for node in G.nodes()]
        
        nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=1000)
        
        # Draw node labels
        node_labels = {node: G.nodes[node]['label'] for node in G.nodes()}
        label_pos = {node: (coords[0], coords[1] - 0.17) for node, coords in pos.items()}
        nx.draw_networkx_labels(G, label_pos, node_labels, font_size=8)
        
        # Draw edges with curves and labels
        for (u, v, data) in G.edges(data=True):
            # Draw curved edge
            nx.draw_networkx_edges(G, pos, 
                                 edgelist=[(u, v)],
                                 edge_color='gray',
                                 arrows=True,
                                 arrowsize=20,
                                 connectionstyle='arc3,rad=0.5',
                                 node_size=3000)
            
            # Calculate edge label position (above the curved edge)
            edge_center = ((pos[u][0] + pos[v][0])/2, 
                          (pos[u][1] + pos[v][1])/2 + 0.2)  # Offset above the edge
            
            # Add edge label
            plt.text(edge_center[0], edge_center[1],
                    data['label'],
                    horizontalalignment='center',
                    verticalalignment='center',
                    fontsize=8,
                    bbox=dict(facecolor='white', edgecolor='none', alpha=0.7))
        
        # Add legend
        legend_elements = [
            mpatches.Patch(color='lightcoral', label='Person'),
            mpatches.Patch(color='lightblue', label='Location'),
            mpatches.Patch(color='lightgreen', label='Item')
        ]
        plt.legend(handles=legend_elements)
        
        plt.title("Pink Panther Heist - Property Graph")
        plt.axis('off')
        plt.show()

    def create_knowledge_graph(self):
        """
        Creates and visualizes a knowledge graph representation of the Pink Panther heist
        """
        print("\n=== Knowledge Graph Example ===")
        print("Demonstrating semantic relationships and inferences")
        
        # Create a new RDF graph
        g = Graph()
        
        # Create namespace
        PP = Namespace("http://pp.org/")
        
        # Define all triples
        triples = [
            (PP.PinkPantherDiamond, RDF.type, PP.Jewel),
            (PP.PinkPantherDiamond, PP.hasValue, Literal("50000000")),
            (PP.PinkPantherDiamond, PP.storedIn, PP.CasinoRoyaleVault),
            (PP.ThePhantom, RDF.type, PP.Criminal),
            (PP.HeistEvent_001, PP.hasTarget, PP.PinkPantherDiamond),
            (PP.HeistEvent_001, PP.hasPerpetrator, PP.ThePhantom),
            (PP.HeistEvent_001, PP.occuredAt, PP.CasinoRoyale),
            (PP.HeistEvent_001, PP.occuredOn, Literal("2024-03-15")),
            (PP.HeistEvent_001, RDF.type, PP.Event),
            (PP.CasinoRoyaleVault, RDF.type, PP.Location),
            (PP.CasinoRoyale, RDF.type, PP.Location)
        ]
        
        # Add all triples to the graph
        for triple in triples:
            g.add(triple)
        
        # Print the triples
        print("\nKnowledge Graph Triples:")
        for subj, pred, obj in g:
            print(f"{subj.n3()} {pred.n3()} {obj.n3()}")
        
        # Example of a simple query
        print("\nQuery Example - Find all events involving the Pink Panther Diamond:")
        qres = g.query(
            """
            SELECT ?event ?perpetrator ?date
            WHERE {
                ?event ?p ?o .
                ?event pp:hasTarget pp:PinkPantherDiamond .
                ?event pp:hasPerpetrator ?perpetrator .
                ?event pp:occuredOn ?date .
            }
            """,
            initNs={"pp": PP}
        )
        
        for row in qres:
            print(f"Event: {row.event}, Perpetrator: {row.perpetrator}, Date: {row.date}")

        # Create visualization graph
        G = nx.DiGraph()
        
        # Extract nodes and their types from triples
        nodes = set()
        node_types = {}
        
        for subj, pred, obj in triples:
            # Add subject node
            nodes.add(str(subj).split('/')[-1])  # Get the last part of the URI
            
            # Add object node if it's not a literal
            if not isinstance(obj, Literal):
                nodes.add(str(obj).split('/')[-1])
            
            # Determine node types based on RDF type relationships
            if pred == RDF.type:
                node_types[str(subj).split('/')[-1]] = str(obj).split('/')[-1].lower()
            
        # Add nodes to graph
        for node in nodes:
            G.add_node(node, node_type=node_types.get(node, 'default'))
        
        # Add edges from triples
        for subj, pred, obj in triples:
            if not isinstance(obj, Literal):  # Skip edges to literal values
                source = str(subj).split('/')[-1]
                target = str(obj).split('/')[-1]
                # Clean up the edge label
                if pred == RDF.type:
                    label = 'type'
                else:
                    label = str(pred).split('/')[-1]
                G.add_edge(source, target, label=label)
        
        # Visualize the knowledge graph
        plt.figure(figsize=self.fig_size)
        pos = nx.spring_layout(G, k=1, iterations=50, seed=3)
        
        # Draw nodes with different colors for different types
        node_colors = ['lightgreen' if G.nodes[node]['node_type'] == 'jewel'
                      else 'lightgray' if G.nodes[node]['node_type'] == 'criminal'
                      else 'lightblue' if G.nodes[node]['node_type'] == 'location'
                      else 'lightcoral' if G.nodes[node]['node_type'] == 'event'
                      else 'peachpuff' for node in G.nodes()]
        
        nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=1000)
        
        # Draw labels below nodes
        label_pos = {node: (coords[0], coords[1] - 0.15) for node, coords in pos.items()}
        nx.draw_networkx_labels(G, label_pos)
        
        # Draw edges and labels
        nx.draw_networkx_edges(G, pos, 
                             edge_color='black',
                             arrows=True, 
                             arrowsize=30,
                             width=2,
                             connectionstyle='arc3,rad=0.15',
                             node_size=1000)
        edge_labels = nx.get_edge_attributes(G, 'label')
        nx.draw_networkx_edge_labels(G, pos, 
                                   edge_labels, 
                                   font_size=8,
                                   bbox=dict(facecolor='white', 
                                           edgecolor='none', 
                                           alpha=0.7))
        
        # Add legend
        legend_elements = [
            mpatches.Patch(color='lightgreen', label='Jewel'),
            mpatches.Patch(color='lightgray', label='Criminal'),
            mpatches.Patch(color='lightblue', label='Location'),
            mpatches.Patch(color='lightcoral', label='Event'),
            mpatches.Patch(color='peachpuff', label='Default')
        ]
        plt.legend(handles=legend_elements)
        
        plt.title("Pink Panther Heist - Knowledge Graph")
        plt.axis('off')
        plt.show()

def main():
    graphs = PinkPantherGraphs()
    
    # Create and display each type of graph
    graphs.create_network_graph()
    graphs.create_property_graph()
    graphs.create_knowledge_graph()

if __name__ == "__main__":
    main() 