Back to PHICodepen Playground
Codepen Playground

Mesh

Network Dataviz

live renderhtml
htmlv.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Afrho-Pod Hypergraph Visualization</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
            color: white;
        }
        
        #container {
            position: absolute;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
        
        #info {
            position: absolute;
            top: 10px;
            left: 10px;
            background: rgba(0, 0, 0, 0.7);
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 14px;
            z-index: 100;
        }
        
        #controls {
            position: absolute;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.7);
            padding: 15px;
            border-radius: 8px;
            z-index: 100;
        }
        
        .control-group {
            margin-bottom: 10px;
        }
        
        .control-group label {
            display: block;
            margin-bottom: 5px;
            font-size: 12px;
            color: #ccc;
        }
        
        button {
            background: #4a6bff;
            color: white;
            border: none;
            padding: 8px 15px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            margin-right: 5px;
            transition: background 0.3s;
        }
        
        button:hover {
            background: #3a5bef;
        }
        
        select, input {
            background: rgba(255, 255, 255, 0.1);
            color: white;
            border: 1px solid rgba(255, 255, 255, 0.2);
            padding: 8px;
            border-radius: 4px;
            font-size: 12px;
            width: 100%;
        }
        
        #stats {
            position: absolute;
            bottom: 10px;
            left: 10px;
            background: rgba(0, 0, 0, 0.7);
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 12px;
            z-index: 100;
        }
        
        #node-info {
            position: absolute;
            bottom: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.8);
            padding: 15px;
            border-radius: 8px;
            font-size: 12px;
            max-width: 300px;
            display: none;
            z-index: 100;
        }
        
        .loading {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #4a6bff;
            font-size: 18px;
            z-index: 100;
        }
        
        .metric-card {
            background: rgba(0, 0, 0, 0.5);
            padding: 10px;
            border-radius: 6px;
            margin: 5px 0;
        }
        
        .metric-label {
            font-size: 10px;
            color: #aaa;
            margin-bottom: 3px;
        }
        
        .metric-value {
            font-size: 16px;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div id="loading" class="loading">Loading hypergraph visualization...</div>
    
    <div id="info">
        <strong>Afrho-Pod Hypergraph Visualization</strong>
        <div>Interactive 3D network topology explorer</div>
    </div>
    
    <div id="controls">
        <div class="control-group">
            <label>Visualization Mode</label>
            <select id="mode-select">
                <option value="force">Force-Directed Graph</option>
                <option value="hypergraph">Hypergraph</option>
                <option value="3d">3D Network</option>
                <option value="organic">Organic Art</option>
            </select>
        </div>
        
        <div class="control-group">
            <label>Node Type Filter</label>
            <select id="node-filter">
                <option value="all">All Nodes</option>
                <option value="server">Servers</option>
                <option value="pod">Pods</option>
                <option value="device">Devices</option>
                <option value="vpn">VPN</option>
            </select>
        </div>
        
        <div class="control-group">
            <button id="refresh-btn">Refresh Data</button>
            <button id="reset-btn">Reset View</button>
        </div>
        
        <div class="control-group">
            <label>Metrics</label>
            <button id="toggle-metrics">Show/Hide</button>
        </div>
    </div>
    
    <div id="stats">
        <div class="metric-card">
            <div class="metric-label">Total Nodes</div>
            <div class="metric-value" id="node-count">0</div>
        </div>
        <div class="metric-card">
            <div class="metric-label">Total Edges</div>
            <div class="metric-value" id="edge-count">0</div>
        </div>
        <div class="metric-card">
            <div class="metric-label">Active Connections</div>
            <div class="metric-value" id="active-connections">0</div>
        </div>
    </div>
    
    <div id="node-info">
        <h3 id="node-title">Node Information</h3>
        <div id="node-details"></div>
    </div>
    
    <div id="container"></div>
    
    <!-- Three.js and other libraries -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/TrackballControls.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/geometries/ConvexGeometry.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/geometries/ConvexBufferGeometry.js"></script>
    
    <!-- D3.js for force-directed graph -->
    <script src="https://d3js.org/d3.v7.min.js"></script>
    
    <script>
        // Global variables
        let scene, camera, renderer, controls;
        let nodes = [], edges = [], nodeObjects = [], edgeObjects = [];
        let selectedNode = null;
        let visualizationMode = 'force';
        let nodeFilter = 'all';
        
        // Network data - this would be fetched from API in real implementation
        let networkData = {
            nodes: [],
            edges: []
        };
        
        // Color schemes
        const colorSchemes = {
            server: 0x4a6bff,
            pod: 0x28a745,
            device: 0xffc107,
            vpn: 0xdc3545,
            container: 0x6a5acd,
            vm: 0x17a2b8
        };
        
        // Initialize the scene
        function init() {
            // Create scene
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a0e27);
            
            // Create camera
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
            camera.position.set(0, 0, 1000);
            
            // Create renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            document.getElementById('container').appendChild(renderer.domElement);
            
            // Create controls
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.screenSpacePanning = false;
            controls.minDistance = 100;
            controls.maxDistance = 5000;
            
            // Add lights
            const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
            scene.add(ambientLight);
            
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
            directionalLight.position.set(1, 1, 1);
            scene.add(directionalLight);
            
            // Add grid helper
            const gridHelper = new THREE.GridHelper(2000, 20, 0x333333, 0x333333);
            scene.add(gridHelper);
            
            // Add axes helper
            const axesHelper = new THREE.AxesHelper(500);
            scene.add(axesHelper);
            
            // Load sample data
            loadSampleData();
            
            // Set up event listeners
            setupEventListeners();
            
            // Start animation loop
            animate();
            
            // Hide loading
            document.getElementById('loading').style.display = 'none';
        }
        
        // Load sample data (in real implementation, this would be fetched from API)
        function loadSampleData() {
            // Sample network topology
            networkData = {
                nodes: [
                    { id: 1, name: 'rhiz-ueth', type: 'server', x: 0, y: 0, z: 0, size: 30, color: colorSchemes.server },
                    { id: 2, name: 'afrho-pod', type: 'pod', x: 200, y: 100, z: 0, size: 25, color: colorSchemes.pod },
                    { id: 3, name: 'exosystem', type: 'server', x: -200, y: 100, z: 0, size: 20, color: colorSchemes.server },
                    { id: 4, name: 'wg0', type: 'vpn', x: 100, y: -100, z: 0, size: 15, color: colorSchemes.vpn },
                    { id: 5, name: 'operatau-sozv', type: 'vpn', x: -100, y: -100, z: 0, size: 15, color: colorSchemes.vpn },
                    { id: 6, name: 'limesdr', type: 'device', x: 300, y: 200, z: 0, size: 10, color: colorSchemes.device },
                    { id: 7, name: 'arduino', type: 'device', x: 350, y: -50, z: 0, size: 10, color: colorSchemes.device },
                    { id: 8, name: 'yubikey', type: 'device', x: 250, y: -150, z: 0, size: 10, color: colorSchemes.device }
                ],
                edges: [
                    { source: 1, target: 2, type: 'vpn', strength: 0.5 },
                    { source: 1, target: 3, type: 'network', strength: 0.3 },
                    { source: 1, target: 4, type: 'vpn', strength: 0.4 },
                    { source: 1, target: 5, type: 'vpn', strength: 0.4 },
                    { source: 2, target: 4, type: 'vpn', strength: 0.2 },
                    { source: 2, target: 5, type: 'vpn', strength: 0.2 },
                    { source: 2, target: 6, type: 'usb', strength: 0.1 },
                    { source: 2, target: 7, type: 'usb', strength: 0.1 },
                    { source: 2, target: 8, type: 'usb', strength: 0.1 }
                ]
            };
            
            // Update stats
            updateStats();
            
            // Create visualization based on current mode
            updateVisualization();
        }
        
        // Update statistics display
        function updateStats() {
            document.getElementById('node-count').textContent = networkData.nodes.length;
            document.getElementById('edge-count').textContent = networkData.edges.length;
            document.getElementById('active-connections').textContent = networkData.edges.length;
        }
        
        // Update visualization based on current mode
        function updateVisualization() {
            // Clear existing objects
            clearScene();
            
            if (visualizationMode === 'force') {
                createForceDirectedGraph();
            } else if (visualizationMode === 'hypergraph') {
                createHypergraph();
            } else if (visualizationMode === '3d') {
                create3DNetwork();
            } else if (visualizationMode === 'organic') {
                createOrganicArt();
            }
        }
        
        // Clear the scene
        function clearScene() {
            // Remove all node and edge objects
            nodeObjects.forEach(obj => scene.remove(obj));
            edgeObjects.forEach(obj => scene.remove(obj));
            
            nodeObjects = [];
            edgeObjects = [];
        }
        
        // Create force-directed graph visualization
        function createForceDirectedGraph() {
            // Create nodes
            networkData.nodes.forEach(node => {
                if (nodeFilter === 'all' || node.type === nodeFilter) {
                    const geometry = new THREE.SphereGeometry(node.size, 32, 32);
                    const material = new THREE.MeshPhongMaterial({
                        color: node.color,
                        shininess: 30,
                        transparent: true,
                        opacity: 0.8
                    });
                    
                    const sphere = new THREE.Mesh(geometry, material);
                    sphere.position.set(node.x, node.y, node.z);
                    sphere.userData = node;
                    
                    scene.add(sphere);
                    nodeObjects.push(sphere);
                }
            });
            
            // Create edges
            networkData.edges.forEach(edge => {
                const sourceNode = networkData.nodes.find(n => n.id === edge.source);
                const targetNode = networkData.nodes.find(n => n.id === edge.target);
                
                if (sourceNode && targetNode) {
                    const sourceObj = nodeObjects.find(obj => obj.userData.id === edge.source);
                    const targetObj = nodeObjects.find(obj => obj.userData.id === edge.target);
                    
                    if (sourceObj && targetObj) {
                        const material = new THREE.LineBasicMaterial({
                            color: 0x666666,
                            linewidth: edge.strength * 5,
                            transparent: true,
                            opacity: 0.6
                        });
                        
                        const geometry = new THREE.BufferGeometry();
                        const positions = [
                            sourceObj.position.x, sourceObj.position.y, sourceObj.position.z,
                            targetObj.position.x, targetObj.position.y, targetObj.position.z
                        ];
                        
                        geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
                        const line = new THREE.Line(geometry, material);
                        
                        scene.add(line);
                        edgeObjects.push(line);
                    }
                }
            });
            
            // Add labels
            addLabels();
        }
        
        // Create hypergraph visualization
        function createHypergraph() {
            // This would create a more complex hypergraph visualization
            // For now, we'll use a similar approach to force-directed but with different styling
            createForceDirectedGraph();
        }
        
        // Create 3D network visualization
        function create3DNetwork() {
            // Create nodes in 3D space
            networkData.nodes.forEach((node, index) => {
                if (nodeFilter === 'all' || node.type === nodeFilter) {
                    const geometry = new THREE.SphereGeometry(node.size, 32, 32);
                    const material = new THREE.MeshPhongMaterial({
                        color: node.color,
                        shininess: 30
                    });
                    
                    const sphere = new THREE.Mesh(geometry, material);
                    
                    // Position nodes in 3D space
                    const angle = (index / networkData.nodes.length) * Math.PI * 2;
                    const radius = 500;
                    sphere.position.set(
                        Math.cos(angle) * radius,
                        (Math.random() - 0.5) * 200,
                        Math.sin(angle) * radius
                    );
                    
                    sphere.userData = node;
                    scene.add(sphere);
                    nodeObjects.push(sphere);
                }
            });
            
            // Create curved edges
            networkData.edges.forEach(edge => {
                const sourceNode = networkData.nodes.find(n => n.id === edge.source);
                const targetNode = networkData.nodes.find(n => n.id === edge.target);
                
                if (sourceNode && targetNode) {
                    const sourceObj = nodeObjects.find(obj => obj.userData.id === edge.source);
                    const targetObj = nodeObjects.find(obj => obj.userData.id === edge.target);
                    
                    if (sourceObj && targetObj) {
                        // Create a curved line
                        const curve = new THREE.QuadraticBezierCurve3(
                            sourceObj.position,
                            new THREE.Vector3(
                                (sourceObj.position.x + targetObj.position.x) / 2,
                                (sourceObj.position.y + targetObj.position.y) / 2 + 200,
                                (sourceObj.position.z + targetObj.position.z) / 2
                            ),
                            targetObj.position
                        );
                        
                        const points = curve.getPoints(50);
                        const geometry = new THREE.BufferGeometry().setFromPoints(points);
                        
                        const material = new THREE.LineBasicMaterial({
                            color: 0x666666,
                            linewidth: edge.strength * 3
                        });
                        
                        const curveObject = new THREE.Line(geometry, material);
                        scene.add(curveObject);
                        edgeObjects.push(curveObject);
                    }
                }
            });
            
            // Add labels
            addLabels();
        }
        
        // Create organic art visualization
        function createOrganicArt() {
            // This would create a more artistic, organic visualization
            // For now, we'll create a simple particle system
            
            // Create a particle system for nodes
            const particleCount = networkData.nodes.length * 10;
            const particles = new THREE.BufferGeometry();
            const positions = [];
            const colors = [];
            const sizes = [];
            
            networkData.nodes.forEach(node => {
                if (nodeFilter === 'all' || node.type === nodeFilter) {
                    for (let i = 0; i < 10; i++) {
                        const angle = Math.random() * Math.PI * 2;
                        const distance = Math.random() * node.size * 2;
                        
                        positions.push(
                            node.x + Math.cos(angle) * distance,
                            node.y + Math.sin(angle) * distance,
                            node.z + (Math.random() - 0.5) * node.size
                        );
                        
                        colors.push(
                            (node.color >> 16) / 255,
                            ((node.color >> 8) & 0xFF) / 255,
                            (node.color & 0xFF) / 255
                        );
                        
                        sizes.push(Math.random() * 5 + 2);
                    }
                }
            });
            
            particles.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
            particles.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
            particles.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
            
            const particleMaterial = new THREE.PointsMaterial({
                size: 5,
                vertexColors: true,
                transparent: true,
                opacity: 0.8
            });
            
            const particleSystem = new THREE.Points(particles, particleMaterial);
            scene.add(particleSystem);
            nodeObjects.push(particleSystem);
            
            // Add some organic-looking connections
            networkData.edges.forEach(edge => {
                const sourceNode = networkData.nodes.find(n => n.id === edge.source);
                const targetNode = networkData.nodes.find(n => n.id === edge.target);
                
                if (sourceNode && targetNode) {
                    const sourcePos = new THREE.Vector3(sourceNode.x, sourceNode.y, sourceNode.z);
                    const targetPos = new THREE.Vector3(targetNode.x, targetNode.y, targetNode.z);
                    
                    // Create a sine wave connection
                    const points = [];
                    const segments = 50;
                    
                    for (let i = 0; i <= segments; i++) {
                        const t = i / segments;
                        const x = sourcePos.x + (targetPos.x - sourcePos.x) * t;
                        const y = sourcePos.y + (targetPos.y - sourcePos.y) * t + Math.sin(t * Math.PI * 2) * 50;
                        const z = sourcePos.z + (targetPos.z - sourcePos.z) * t;
                        
                        points.push(new THREE.Vector3(x, y, z));
                    }
                    
                    const geometry = new THREE.BufferGeometry().setFromPoints(points);
                    const material = new THREE.LineBasicMaterial({
                        color: 0x666666,
                        linewidth: edge.strength * 2,
                        transparent: true,
                        opacity: 0.5
                    });
                    
                    const line = new THREE.Line(geometry, material);
                    scene.add(line);
                    edgeObjects.push(line);
                }
            });
        }
        
        // Add labels to nodes
        function addLabels() {
            // Remove existing labels
            const existingLabels = scene.children.filter(obj => obj.userData && obj.userData.type === 'label');
            existingLabels.forEach(label => scene.remove(label));
            
            // Add new labels
            nodeObjects.forEach(nodeObj => {
                if (nodeObj.userData) {
                    const canvas = document.createElement('canvas');
                    const context = canvas.getContext('2d');
                    const fontSize = 24;
                    
                    context.font = `${fontSize}px Arial`;
                    const textWidth = context.measureText(nodeObj.userData.name).width;
                    
                    canvas.width = textWidth + 20;
                    canvas.height = fontSize + 20;
                    
                    context.fillStyle = 'rgba(0, 0, 0, 0.7)';
                    context.fillRect(0, 0, canvas.width, canvas.height);
                    
                    context.fillStyle = 'white';
                    context.font = `${fontSize}px Arial`;
                    context.textAlign = 'center';
                    context.textBaseline = 'middle';
                    context.fillText(nodeObj.userData.name, canvas.width / 2, canvas.height / 2);
                    
                    const texture = new THREE.CanvasTexture(canvas);
                    const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
                    const sprite = new THREE.Sprite(spriteMaterial);
                    
                    sprite.position.copy(nodeObj.position);
                    sprite.position.y += nodeObj.userData.size + 20;
                    sprite.userData = { type: 'label', nodeId: nodeObj.userData.id };
                    
                    scene.add(sprite);
                }
            });
        }
        
        // Set up event listeners
        function setupEventListeners() {
            // Mode selection
            document.getElementById('mode-select').addEventListener('change', function() {
                visualizationMode = this.value;
                updateVisualization();
            });
            
            // Node filter
            document.getElementById('node-filter').addEventListener('change', function() {
                nodeFilter = this.value;
                updateVisualization();
            });
            
            // Refresh button
            document.getElementById('refresh-btn').addEventListener('click', function() {
                // In real implementation, this would fetch new data from API
                console.log('Refreshing data...');
                updateVisualization();
            });
            
            // Reset button
            document.getElementById('reset-btn').addEventListener('click', function() {
                controls.reset();
            });
            
            // Window resize
            window.addEventListener('resize', function() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });
            
            // Node click events
            window.addEventListener('click', function(event) {
                const mouse = new THREE.Vector2();
                mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
                mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
                
                const raycaster = new THREE.Raycaster();
                raycaster.setFromCamera(mouse, camera);
                
                const intersects = raycaster.intersectObjects(nodeObjects.filter(obj => obj.type === 'Mesh'));
                
                if (intersects.length > 0) {
                    const selected = intersects[0].object;
                    showNodeInfo(selected.userData);
                } else {
                    hideNodeInfo();
                }
            });
        }
        
        // Show node information
        function showNodeInfo(node) {
            selectedNode = node;
            
            const infoElement = document.getElementById('node-info');
            const titleElement = document.getElementById('node-title');
            const detailsElement = document.getElementById('node-details');
            
            titleElement.textContent = node.name;
            
            let detailsHTML = `
                <div><strong>Type:</strong> ${node.type}</div>
                <div><strong>ID:</strong> ${node.id}</div>
                <div><strong>Position:</strong> (${node.x.toFixed(1)}, ${node.y.toFixed(1)}, ${node.z.toFixed(1)})</div>
                <div><strong>Size:</strong> ${node.size}</div>
            `;
            
            // Add type-specific information
            if (node.type === 'server') {
                detailsHTML += '<div><strong>Role:</strong> Main Server</div>';
            } else if (node.type === 'pod') {
                detailsHTML += '<div><strong>Role:</strong> IoT Gateway</div>';
            } else if (node.type === 'device') {
                detailsHTML += '<div><strong>Role:</strong> Peripheral Device</div>';
            } else if (node.type === 'vpn') {
                detailsHTML += '<div><strong>Role:</strong> VPN Interface</div>';
            }
            
            detailsElement.innerHTML = detailsHTML;
            infoElement.style.display = 'block';
        }
        
        // Hide node information
        function hideNodeInfo() {
            selectedNode = null;
            document.getElementById('node-info').style.display = 'none';
        }
        
        // Animation loop
        function animate() {
            requestAnimationFrame(animate);
            
            // Update controls
            controls.update();
            
            // Rotate nodes slightly for organic feel
            if (visualizationMode === 'organic') {
                nodeObjects.forEach(node => {
                    if (node.rotation) {
                        node.rotation.x += 0.001;
                        node.rotation.y += 0.002;
                    }
                });
            }
            
            // Render scene
            renderer.render(scene, camera);
        }
        
        // Initialize the visualization
        init();
    </script>
</body>
</html>