# Graph Visualization Feature Checklist **Source**: `/Users/kempersc/apps/example_ld` β†’ GLAM Frontend Integration **Date**: 2025-11-22 **Purpose**: Ensure all D3.js visualization features from example_ld are properly implemented in GLAM --- ## 🎯 Executive Summary The example_ld project contains a **highly sophisticated D3.js force-directed graph visualization** with the following key features: 1. **Bidirectional edge switching** - Click edges to reverse direction 2. **Node pop-up metadata** - Double-click nodes for detailed modal 3. **Edge highlighting** - Hover to see edge labels and highlighting 4. **Label collision avoidance** - Intelligent positioning of edge labels 5. **Multi-degree connection analysis** - Streamgraph showing 1st, 2nd, 3rd degree connections 6. **RDF data extraction** - Click nodes to extract RDF triples in multiple formats 7. **Source XML tracing** - View original source XML for nodes This document maps these features to GLAM's visualization needs and provides implementation guidance. --- ## πŸ“‹ Feature Comparison Matrix | Feature | example_ld Status | GLAM Current Status | GLAM Priority | |---------|------------------|---------------------|---------------| | **Core Graph Visualization** | | Force-directed graph | βœ… Implemented | ❌ Basic (Task 7) | πŸ”΄ CRITICAL | | Node type coloring | βœ… 12 node types | ⚠️ Schema.org only | 🟑 HIGH | | Zoom/pan controls | βœ… D3 zoom | ⚠️ Basic | 🟒 MEDIUM | | Drag-and-drop nodes | βœ… With simulation | ❌ Not impl | 🟑 HIGH | | **Edge Features** | | Bidirectional edges | βœ… Click to switch | ❌ Not impl | πŸ”΄ CRITICAL | | Edge highlighting | βœ… Hover + arrows | ❌ Not impl | πŸ”΄ CRITICAL | | Edge labels | βœ… With collision avoidance | ❌ Not impl | πŸ”΄ CRITICAL | | Curved edge paths | βœ… Quadratic bezier | ❌ Straight lines | 🟑 HIGH | | Arrow markers | βœ… Per node type | ❌ Basic | 🟑 HIGH | | **Node Interaction** | | Click selection | βœ… Highlights node | ⚠️ Basic | 🟑 HIGH | | Double-click modal | βœ… Full metadata | ❌ Not impl | πŸ”΄ CRITICAL | | Hover tooltips | βœ… Rich metadata | ⚠️ Basic | 🟑 HIGH | | Node labels | βœ… Positioned | ⚠️ Basic | 🟒 MEDIUM | | **Advanced Features** | | Streamgraph | βœ… Multi-degree BFS | ❌ Not impl | 🟒 MEDIUM | | Label collision avoidance | βœ… Physics-based | ❌ Not impl | 🟑 HIGH | | RDF extraction per node | βœ… All formats | ❌ Not impl | πŸ”΄ CRITICAL | | Source data tracing | βœ… XML context | ❌ Not impl | 🟑 HIGH | **Legend**: - πŸ”΄ CRITICAL - Must have for MVP - 🟑 HIGH - Important for usability - 🟒 MEDIUM - Nice to have --- ## πŸ” Detailed Feature Analysis ### 1. Bidirectional Edge Switching βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/graph.js // Lines: 30-84, 327-382 // Bidirectional relationship mappings const BIDIRECTIONAL_MAPPINGS = { 'describesOrDescribed': 'isOrWasDescribedBy', 'isOrWasDescribedBy': 'describesOrDescribed', 'includes': 'isIncludedIn', 'isIncludedIn': 'includes', 'hasOrHadHolder': 'isOrWasHolderOf', 'isOrWasHolderOf': 'hasOrHadHolder', // ... 20+ more mappings }; // Click handler on edges link.on('click', function(event, d) { if (!d.isBidirectional) return; event.stopPropagation(); d.isReversed = !d.isReversed; // Swap source and target const temp = d.source; d.source = d.target; d.target = temp; // Update predicate to inverse const inversePredicate = getInversePredicate(d.predicate); if (inversePredicate) { d.predicate = inversePredicate; } // Update arrow marker and label // ... (lines 348-369) }); ``` **GLAM Requirement**: Heritage ontology relationships are inherently bidirectional: - `hasCreator` ↔ `isCreatorOf` - `hasLocation` ↔ `isLocationOf` - `hasCollection` ↔ `isPartOfCollection` - `hasCustodian` ↔ `isCustodianOf` **Implementation Plan**: 1. Create `HERITAGE_BIDIRECTIONAL_MAPPINGS` for GLAM ontology 2. Add click handler to edges in `OntologyVisualizer.tsx` 3. Update Mermaid diagram syntax to show direction changes 4. Add visual feedback (flash animation on direction change) **Files to Create/Modify**: - `frontend/src/lib/graph/bidirectional-mappings.ts` (NEW) - `frontend/src/components/query/OntologyVisualizer.tsx` (MODIFY) - `frontend/src/lib/graph/ontology-parser.ts` (MODIFY) --- ### 2. Node Pop-up Metadata Modal βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/graph.js // Lines: 696-716 function handleNodeDoubleClick(event, d) { event.stopPropagation(); // Store selected node and edges globally window.selectedGraphNode = d; window.selectedGraphLinks = graphData.links.filter(link => link.source.id === d.id || link.target.id === d.id ); // Open Bootstrap modal const modal = new bootstrap.Modal(document.getElementById('nodeDetailsModal')); modal.show(); // Populate modal with node data populateNodeModal(d, window.selectedGraphLinks); } // Modal content includes: // 1. Node metadata (label, type, URI, title, identifier, etc.) // 2. RDF preview in multiple formats (Turtle, JSON-LD, N-Triples, XML) // 3. Source XML with ancestor/descendant context controls // 4. Copy to clipboard functionality ``` **GLAM Requirement**: Heritage institutions have rich metadata that should be accessible via node interaction: - Institution name, type, city, country - ISIL codes, Wikidata IDs, VIAF IDs - Collections held - Digital platforms used - Historical change events (mergers, relocations) **Implementation Plan**: 1. Create React modal component for node details 2. Double-click handler on nodes 3. Fetch full institution record from SPARQL endpoint 4. Display metadata in organized sections 5. Include SPARQL query to retrieve this node's data 6. Add "Explore in Query Builder" button **Files to Create/Modify**: - `frontend/src/components/graph/NodeDetailsModal.tsx` (NEW) - `frontend/src/components/query/OntologyVisualizer.tsx` (MODIFY) - `frontend/src/lib/sparql/node-extraction.ts` (NEW) --- ### 3. Edge Highlighting with Labels βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/graph.js // Lines: 383-426 link.on('mouseenter', function(event, d) { // Show label and background for this edge const linkIndex = graphData.links.indexOf(d); linkLabel.filter((ld, i) => i === linkIndex) .transition() .duration(200) .style('opacity', 1); linkLabelBg.filter((ld, i) => i === linkIndex) .transition() .duration(200) .style('opacity', 1); // Highlight the edge and switch to highlighted arrow const targetType = (typeof d.target === 'object') ? d.target.type : 'Resource'; d3.select(this) .transition() .duration(200) .attr('stroke-opacity', 1) .attr('stroke-width', Math.sqrt(d.value || 1) * 1.5) .attr('marker-end', `url(#arrow-${targetType}-highlight)`); }); link.on('mouseleave', function(event, d) { // Hide label and restore normal appearance // ... (lines 405-425) }); ``` **Key Features**: - Edge labels hidden by default (reduced visual clutter) - Hover shows label with white background for readability - Edge thickness increases on hover - Arrow marker switches to highlighted version (larger, fully opaque) - Smooth 200ms transitions **GLAM Requirement**: Heritage relationships should be discoverable without overwhelming the user: - Show property names on hover (e.g., "has creator", "is located in") - Highlight relationship path when hovering edges - Distinguish between different relationship types by color **Implementation Plan**: 1. Add hidden label layer in Mermaid diagram 2. Implement hover detection with D3 overlay 3. Create arrow marker definitions for each property type 4. Add transition animations for smooth UX **Files to Create/Modify**: - `frontend/src/components/graph/EdgeHighlighter.tsx` (NEW) - `frontend/src/lib/graph/arrow-markers.ts` (NEW) - `frontend/src/components/query/OntologyVisualizer.css` (MODIFY - add hover styles) --- ### 4. Label Collision Avoidance βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/graph.js // Lines: 718-877 /** * Custom force for label collision avoidance * Physics-based simulation that: * 1. Detects bounding box overlaps between edge labels * 2. Applies repulsive forces to separate colliding labels * 3. Maintains spring force to keep labels near their edges * 4. Freezes labels when simulation cools down */ function labelCollisionForce(links) { let strength = 0.5; let iterations = 1; function force(alpha) { // Only apply when simulation active if (alpha > 0.05) { for (let k = 0; k < iterations; k++) { resolveLabelCollisions(links, alpha); } } else { // Freeze labels when cooled links.forEach(link => { link.labelFixed = true; link.labelVx = 0; link.labelVy = 0; }); } } return force; } function resolveLabelCollisions(links, alpha) { const padding = 8; const damping = 0.7; // Check all pairs for collisions for (let i = 0; i < links.length; i++) { for (let j = i + 1; j < links.length; j++) { const linkA = links[i]; const linkB = links[j]; // Calculate bounding boxes const boxA = { left: linkA.labelX - linkA.labelWidth / 2 - padding, right: linkA.labelX + linkA.labelWidth / 2 + padding, top: linkA.labelY - linkA.labelHeight / 2 - padding, bottom: linkA.labelY + linkA.labelHeight / 2 + padding }; // ... check overlap and apply forces } } // Apply spring force to pull labels back to edges // ... (lines 847-876) } ``` **Key Insights**: - Labels have bounding boxes tracked in link data - Collision detection uses AABB (axis-aligned bounding box) algorithm - Repulsive forces push labels apart - Spring forces pull labels back to edge midpoints - Velocity damping prevents oscillation - Labels freeze when simulation cools (performance optimization) **GLAM Requirement**: Ontology diagrams can have many relationships, causing label overlap: - Property names (e.g., "hasCreator", "isLocationOf") - Cardinality constraints (e.g., "0..*", "1..1") - Domain/range annotations **Implementation Plan**: 1. Implement bounding box tracking for Mermaid edge labels 2. Add physics simulation for label positioning 3. Use D3 force simulation or custom collision detection 4. Ensure labels stay readable even in dense graphs **Files to Create/Modify**: - `frontend/src/lib/graph/label-collision.ts` (NEW) - `frontend/src/lib/graph/physics-sim.ts` (NEW) - `frontend/src/components/query/OntologyVisualizer.tsx` (MODIFY - integrate physics) --- ### 5. Multi-Degree Connection Analysis (Streamgraph) βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/streamgraph.js // Lines: 45-52, 187-300 const STREAMGRAPH_CONFIG = { MAX_DEGREE_LEVELS: 3, // Traverse up to 3 degrees MAX_NODES_PER_DEGREE: 10, // Limit per degree MAX_TOTAL_NODES: 30, // Absolute max ENABLE_MULTI_DEGREE: true }; // BFS traversal for multi-degree connections function analyzeNodeConnections(node, links, nodes) { // Build adjacency list const adjacencyList = {}; links.forEach(link => { const sourceId = (typeof link.source === 'object') ? link.source.id : link.source; const targetId = (typeof link.target === 'object') ? link.target.id : link.target; if (!adjacencyList[sourceId]) adjacencyList[sourceId] = []; if (!adjacencyList[targetId]) adjacencyList[targetId] = []; adjacencyList[sourceId].push({ connectedNodeId: targetId, predicate: link.predicate }); adjacencyList[targetId].push({ connectedNodeId: sourceId, predicate: link.predicate }); }); // BFS queue: { nodeId, degreeLevel, pathFromRoot } const queue = [{ nodeId: node.id, degreeLevel: 0, pathFromRoot: [node.id] }]; const visited = new Set([node.id]); const connectedNodes = {}; while (queue.length > 0) { const current = queue.shift(); if (current.degreeLevel >= STREAMGRAPH_CONFIG.MAX_DEGREE_LEVELS) continue; const neighbors = adjacencyList[current.nodeId] || []; for (const neighbor of neighbors) { const nextNodeId = neighbor.connectedNodeId; const predicate = neighbor.predicate; const nextDegreeLevel = current.degreeLevel + 1; if (visited.has(nextNodeId)) continue; if (Object.keys(connectedNodes).length >= STREAMGRAPH_CONFIG.MAX_TOTAL_NODES) { return connectedNodes; } visited.add(nextNodeId); queue.push({ nodeId: nextNodeId, degreeLevel: nextDegreeLevel, pathFromRoot: [...current.pathFromRoot, nextNodeId] }); connectedNodes[nextNodeId] = { nodeId: nextNodeId, degreeLevel: nextDegreeLevel, relationships: {}, pathFromRoot: current.pathFromRoot }; // Track relationship types if (!connectedNodes[nextNodeId].relationships[predicate]) { connectedNodes[nextNodeId].relationships[predicate] = []; } connectedNodes[nextNodeId].relationships[predicate].push(current.nodeId); } } return connectedNodes; } ``` **Visualization Output**: - Streamgraph shows connections grouped by property type - Different colors for different relationship types - Degree levels indicated by shading (darker = closer) - Interactive hover shows specific connections **GLAM Requirement**: Heritage institutions have complex networks: - Direct relationships: Museum β†’ Collection - 2nd degree: Museum β†’ Collection β†’ Creator - 3rd degree: Museum β†’ Collection β†’ Creator β†’ Place Use cases: - Find all institutions connected to a specific creator - Discover institutions holding related collections - Trace provenance chains through custody transfers **Implementation Plan**: 1. Implement BFS graph traversal algorithm 2. Create streamgraph visualization component (D3.js or Chart.js) 3. Add degree level filtering UI 4. Integrate with node click events 5. Show relationship paths in tooltip **Files to Create/Modify**: - `frontend/src/lib/graph/bfs-traversal.ts` (NEW) - `frontend/src/components/graph/Streamgraph.tsx` (NEW) - `frontend/src/lib/graph/connection-analysis.ts` (NEW) - `frontend/src/components/query/OntologyVisualizer.tsx` (MODIFY - add streamgraph panel) --- ### 6. RDF Data Extraction Per Node βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/graph.js // Lines: 1358-1587 // Modal shows RDF data in multiple formats: // 1. Turtle (default) // 2. JSON-LD // 3. N-Triples // 4. RDF/XML async function extractNodeRDFData(node, links, format) { // Get full RDF from cache let data = await window.resultsDB.getResults({ validate: true }); if (!data) { data = JSON.parse(sessionStorage.getItem('transformResults')); } const formatMap = { 'turtle': 'turtle', 'jsonld': 'json-ld', 'ntriples': 'nt', 'xml': 'xml' }; const formatKey = formatMap[format] || 'turtle'; let fullRDF = data.formats[formatKey]; // Extract triples related to this node const nodeURI = node.uri || node.id; const relatedURIs = new Set([nodeURI]); // Add all connected node URIs links.forEach(link => { relatedURIs.add(link.source.uri || link.source.id); relatedURIs.add(link.target.uri || link.target.id); }); // Filter triples by format if (formatKey === 'nt') { // N-Triples - filter line by line const lines = fullRDF.split('\n'); const filtered = lines.filter(line => { if (!line.trim() || line.startsWith('#')) return true; for (const uri of relatedURIs) { if (line.includes(uri)) return true; } return false; }); return filtered.join('\n'); } else if (formatKey === 'turtle') { return filterTurtleBySubjects(fullRDF, relatedURIs); } else if (formatKey === 'json-ld') { const jsonData = JSON.parse(fullRDF); if (jsonData['@graph']) { jsonData['@graph'] = jsonData['@graph'].filter(item => { const itemId = item['@id'] || ''; return Array.from(relatedURIs).some(uri => itemId.includes(uri)); }); } return JSON.stringify(jsonData, null, 2); } return fullRDF; } ``` **Key Features**: - Format selector dropdown (Turtle/JSON-LD/N-Triples/XML) - Filters RDF to show only node + connected nodes - Syntax highlighting via Prism.js - Copy to clipboard button - Shows raw triples that generated the visualization **GLAM Requirement**: Users should be able to: - See the SPARQL-generated RDF for any institution - Understand how the visualization was created - Copy RDF for use in external tools (ProtΓ©gΓ©, TopBraid) - Verify data accuracy by inspecting raw triples **Implementation Plan**: 1. Add SPARQL DESCRIBE query for selected node 2. Create format conversion utilities (SPARQL JSON β†’ Turtle/JSON-LD/N-Triples) 3. Implement syntax highlighting (Prism.js or Monaco) 4. Add RDF preview panel to node details modal 5. Include "Run in Query Builder" button **Files to Create/Modify**: - `frontend/src/lib/sparql/rdf-extraction.ts` (NEW) - `frontend/src/lib/rdf/format-converter.ts` (NEW) - `frontend/src/components/graph/RDFPreview.tsx` (NEW) - `frontend/src/components/graph/NodeDetailsModal.tsx` (MODIFY - add RDF tab) --- ### 7. Source Data Tracing βœ…β†’βŒ **example_ld Implementation**: ```javascript // File: /Users/kempersc/apps/example_ld/static/js/graph.js // Lines: 1589-1744 async function extractSourceXMLWithContext(node, ancestorLevels, descendantLevels) { // Get source XML from cache let data = await window.resultsDB.getResults({ validate: true }); const sourceXML = data.source_xml; // Parse XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(sourceXML, 'text/xml'); // Find element by ID const nodeId = extractIdFromURI(node.uri || node.id); let element = xmlDoc.querySelector(`[id="${nodeId}"]`); // Fallback searches if not found if (!element) { element = xmlDoc.querySelector(`[unitid="${nodeId}"]`); } // ... more fallback searches (lines 1638-1660) // Navigate up ancestor levels let contextElement = element; for (let i = 0; i < ancestorLevels && contextElement.parentElement; i++) { contextElement = contextElement.parentElement; } // Clone and prune descendants const clonedElement = contextElement.cloneNode(true); if (descendantLevels >= 0 && descendantLevels < 100) { pruneDescendants(clonedElement, descendantLevels, 0); } // Format and return const serializer = new XMLSerializer(); let xmlString = serializer.serializeToString(clonedElement); return formatXML(xmlString); } ``` **UI Controls**: - Slider for ancestor levels (0-5) - Slider for descendant levels (0-10) - "Include Source XML" checkbox - Shows hierarchical context around node **GLAM Requirement**: Heritage data comes from CSV files, not XML, but tracing is still valuable: - Show original CSV row that generated this institution - Display provenance: which file, line number, extraction date - Link back to data tier (TIER_1_AUTHORITATIVE vs TIER_4_INFERRED) - Show data quality confidence scores **Implementation Plan**: 1. Store CSV provenance in RDF as PROV-O triples 2. SPARQL query to retrieve provenance for node 3. Display CSV row in modal with field highlighting 4. Add "View Full Record in CSV" link 5. Show data quality indicators (tier, confidence) **Files to Create/Modify**: - `frontend/src/lib/provenance/csv-tracer.ts` (NEW) - `frontend/src/components/graph/ProvenanceView.tsx` (NEW) - `frontend/src/components/graph/NodeDetailsModal.tsx` (MODIFY - add provenance tab) --- ## πŸ—οΈ Architecture Integration ### Component Hierarchy (Proposed) ``` QueryBuilder (existing) β”œβ”€β”€ OntologyVisualizer (existing) β”‚ β”œβ”€β”€ MermaidRenderer (existing) β”‚ β”œβ”€β”€ D3Overlay (NEW) ← Intercepts interactions β”‚ β”‚ β”œβ”€β”€ EdgeInteractionLayer (NEW) β”‚ β”‚ β”‚ β”œβ”€β”€ BidirectionalSwitcher β”‚ β”‚ β”‚ β”œβ”€β”€ EdgeHighlighter β”‚ β”‚ β”‚ └── LabelCollisionManager β”‚ β”‚ β”œβ”€β”€ NodeInteractionLayer (NEW) β”‚ β”‚ β”‚ β”œβ”€β”€ HoverTooltip β”‚ β”‚ β”‚ β”œβ”€β”€ ClickHandler β”‚ β”‚ β”‚ └── DoubleClickHandler β”‚ β”‚ └── ForceSimulation (NEW) β”‚ β”‚ β”œβ”€β”€ LabelPhysics β”‚ β”‚ └── NodePositioning β”‚ β”œβ”€β”€ ZoomControls (existing) β”‚ └── SVGExporter (existing) β”œβ”€β”€ NodeDetailsModal (NEW) β”‚ β”œβ”€β”€ MetadataView β”‚ β”œβ”€β”€ RDFPreview (NEW) β”‚ β”‚ β”œβ”€β”€ FormatSelector β”‚ β”‚ β”œβ”€β”€ SyntaxHighlighter β”‚ β”‚ └── CopyButton β”‚ β”œβ”€β”€ ProvenanceView (NEW) β”‚ β”‚ β”œβ”€β”€ CSVTracer β”‚ β”‚ └── DataQualityIndicator β”‚ └── ConnectionAnalysis (NEW) β”‚ └── Streamgraph (NEW) └── GraphToolbar (NEW) β”œβ”€β”€ LayoutSelector β”œβ”€β”€ FilterControls └── ExportOptions ``` ### Data Flow ``` SPARQL Endpoint (Oxigraph) ↓ SPARQL CONSTRUCT query (get ontology triples) ↓ RDF Parser (n3.js or rdflib) ↓ Graph Data Structure { nodes, edges } ↓ Mermaid Diagram Generation (existing) ↓ D3 Overlay (NEW) ← Adds interactivity ↓ User Interactions: - Click edge β†’ Switch direction - Double-click node β†’ Open modal - Hover edge β†’ Show label - Single-click node β†’ Highlight connections ``` --- ## πŸ“ Implementation Priorities ### Phase 1: Core Interactivity (Week 1) **Goal**: Make the graph interactive with basic features - [ ] **Task 1.1**: Add D3 overlay to OntologyVisualizer - Detect Mermaid SVG elements - Attach D3 selection to nodes and edges - Implement basic click/hover detection - **Estimated**: 4 hours - [ ] **Task 1.2**: Implement edge highlighting - Show edge labels on hover - Highlight edges with thicker stroke - Add transition animations - **Estimated**: 3 hours - [ ] **Task 1.3**: Add node hover tooltips - Extract metadata from RDF - Show tooltip with node info - Position tooltip near cursor - **Estimated**: 2 hours - [ ] **Task 1.4**: Implement node click handler - Highlight clicked node - Show connected edges - Update UI to show selection - **Estimated**: 2 hours **Phase 1 Total**: ~11 hours (1.5 days) --- ### Phase 2: Bidirectional Edges (Week 1-2) **Goal**: Enable edge direction switching - [ ] **Task 2.1**: Create bidirectional mappings for GLAM ontology - Map heritage properties (hasCreator ↔ isCreatorOf) - Include CPOV, TOOI, Schema.org mappings - **Estimated**: 3 hours - [ ] **Task 2.2**: Implement edge click handler - Detect bidirectional relationships - Swap source/target on click - Update arrow direction - **Estimated**: 4 hours - [ ] **Task 2.3**: Update Mermaid diagram on direction change - Regenerate Mermaid syntax - Re-render diagram - Maintain zoom/pan state - **Estimated**: 5 hours - [ ] **Task 2.4**: Add visual feedback - Flash animation on direction change - Update label text - Show "bidirectional" indicator - **Estimated**: 2 hours **Phase 2 Total**: ~14 hours (2 days) --- ### Phase 3: Node Details Modal (Week 2) **Goal**: Rich node metadata display - [ ] **Task 3.1**: Create NodeDetailsModal component - Bootstrap 5 modal with tabs - Metadata tab layout - Close/navigation controls - **Estimated**: 3 hours - [ ] **Task 3.2**: Implement double-click handler - Detect double-click on nodes - Open modal with node data - Populate metadata fields - **Estimated**: 2 hours - [ ] **Task 3.3**: Add RDF preview tab - SPARQL DESCRIBE query for node - Format selector (Turtle/JSON-LD/N-Triples) - Syntax highlighting with Prism.js - **Estimated**: 5 hours - [ ] **Task 3.4**: Add provenance view tab - Show CSV source data - Display data tier and confidence - Link to full record - **Estimated**: 4 hours - [ ] **Task 3.5**: Add copy-to-clipboard - Copy RDF data - Copy metadata as JSON - Copy SPARQL query - **Estimated**: 2 hours **Phase 3 Total**: ~16 hours (2 days) --- ### Phase 4: Label Collision Avoidance (Week 3) **Goal**: Intelligent edge label positioning - [ ] **Task 4.1**: Implement bounding box tracking - Calculate label dimensions - Store in edge data - Update on diagram changes - **Estimated**: 3 hours - [ ] **Task 4.2**: Add collision detection - AABB overlap algorithm - Check all label pairs - Identify collisions - **Estimated**: 4 hours - [ ] **Task 4.3**: Implement physics simulation - Repulsive forces between labels - Spring forces to edges - Velocity damping - **Estimated**: 6 hours - [ ] **Task 4.4**: Integrate with Mermaid - Position labels via CSS transforms - Curve edges around labels - Freeze labels when settled - **Estimated**: 5 hours **Phase 4 Total**: ~18 hours (2.5 days) --- ### Phase 5: Advanced Features (Week 4) **Goal**: Streamgraph and connection analysis - [ ] **Task 5.1**: Implement BFS graph traversal - Build adjacency list from RDF - Multi-degree traversal (1st, 2nd, 3rd) - Track paths and degree levels - **Estimated**: 5 hours - [ ] **Task 5.2**: Create Streamgraph component - D3.js streamgraph layout - Color coding by property type - Interactive hover - **Estimated**: 8 hours - [ ] **Task 5.3**: Integrate streamgraph with node selection - Trigger on node click - Update when selection changes - Show connection statistics - **Estimated**: 4 hours - [ ] **Task 5.4**: Add connection filtering UI - Degree level slider - Property type checkboxes - Max nodes limit - **Estimated**: 3 hours **Phase 5 Total**: ~20 hours (2.5 days) --- ## πŸ“¦ Dependencies ### NPM Packages to Install ```json { "dependencies": { "d3": "^7.8.5", // D3.js for graph manipulation "d3-force": "^3.0.0", // Force simulation for physics "d3-zoom": "^3.0.0", // Zoom and pan controls "n3": "^1.17.2", // RDF parsing (Turtle, N-Triples) "rdflib": "^2.2.33", // Alternative RDF library "prismjs": "^1.29.0" // Syntax highlighting for RDF }, "devDependencies": { "@types/d3": "^7.4.3", "@types/prismjs": "^1.26.3" } } ``` ### Existing Dependencies (Reuse) - βœ… `mermaid` (already installed for diagrams) - βœ… `@monaco-editor/react` (syntax highlighting fallback) - βœ… `react` and `react-dom` (UI framework) --- ## πŸ§ͺ Testing Strategy ### Unit Tests **Edge Interaction**: ```typescript // tests/unit/graph/BidirectionalEdges.test.ts describe('BidirectionalEdges', () => { it('should detect bidirectional relationships', () => { const edge = { predicate: 'hasCreator' }; expect(isBidirectional(edge)).toBe(true); }); it('should swap source and target on click', () => { const edge = { source: 'A', target: 'B', predicate: 'hasCreator' }; const swapped = swapEdgeDirection(edge); expect(swapped.source).toBe('B'); expect(swapped.target).toBe('A'); expect(swapped.predicate).toBe('isCreatorOf'); }); }); ``` **Label Collision**: ```typescript // tests/unit/graph/LabelCollision.test.ts describe('LabelCollision', () => { it('should detect overlapping labels', () => { const labelA = { x: 100, y: 100, width: 50, height: 20 }; const labelB = { x: 110, y: 105, width: 50, height: 20 }; expect(detectCollision(labelA, labelB)).toBe(true); }); it('should apply repulsive forces to colliding labels', () => { const labelA = { x: 100, y: 100, vx: 0, vy: 0 }; const labelB = { x: 110, y: 105, vx: 0, vy: 0 }; applyCollisionForce(labelA, labelB); expect(labelA.vx).toBeLessThan(0); // Pushed left expect(labelB.vx).toBeGreaterThan(0); // Pushed right }); }); ``` ### Integration Tests **Node Modal Workflow**: ```typescript // tests/integration/NodeModalFlow.test.tsx describe('NodeModalFlow', () => { it('should open modal on double-click and display RDF', async () => { const { container } = render(); // Find a node in the SVG const node = container.querySelector('circle.graph-node'); fireEvent.doubleClick(node); // Wait for modal to open await waitFor(() => { expect(screen.getByText('Node Details')).toBeInTheDocument(); }); // Check RDF preview tab fireEvent.click(screen.getByText('RDF Preview')); await waitFor(() => { expect(screen.getByText('Turtle')).toBeInTheDocument(); }); }); }); ``` ### Visual Regression Tests **Graph Rendering**: ```typescript // tests/visual/GraphVisualization.visual.test.tsx describe('GraphVisualization', () => { it('should render graph with correct node positions', async () => { const { container } = render(); // Take screenshot const screenshot = await page.screenshot(); // Compare with baseline expect(screenshot).toMatchImageSnapshot({ failureThreshold: 0.01, failureThresholdType: 'percent' }); }); }); ``` --- ## πŸ“š Documentation Requirements ### User Documentation **Interaction Guide** (`frontend/docs/GRAPH_INTERACTIONS.md`): ```markdown # Graph Visualization Interactions ## Basic Navigation - **Zoom**: Mouse wheel or pinch gesture - **Pan**: Click and drag background - **Reset view**: Click "Reset Zoom" button ## Node Interactions - **Hover**: Shows tooltip with node metadata - **Single-click**: Highlights node and connected edges - **Double-click**: Opens detailed modal with RDF data ## Edge Interactions - **Hover**: Shows edge label (property name) - **Click**: Switches direction (for bidirectional properties) - **Highlighted edges**: Indicated connected nodes ## Advanced Features - **Streamgraph**: Click node to see multi-degree connections - **RDF Preview**: View raw triples in multiple formats - **Provenance**: Trace data back to source CSV ``` ### Developer Documentation **Architecture Guide** (`frontend/docs/GRAPH_ARCHITECTURE.md`): ```markdown # Graph Visualization Architecture ## Component Overview - OntologyVisualizer: Main container - D3Overlay: Interactive layer on top of Mermaid - NodeDetailsModal: Rich metadata display - Streamgraph: Connection analysis ## Data Flow 1. SPARQL query β†’ RDF triples 2. RDF parser β†’ Graph data structure 3. Mermaid β†’ SVG diagram 4. D3 overlay β†’ Interactive events 5. User interaction β†’ State updates β†’ UI changes ## Extending the Graph - Add new node types: Update `getNodeColor()` function - Add new edge properties: Update `BIDIRECTIONAL_MAPPINGS` - Customize layout: Modify force simulation parameters ``` --- ## 🎯 Success Metrics ### User Experience Metrics - **Interaction Latency**: < 100ms for hover, < 200ms for click - **Graph Load Time**: < 2s for 50 nodes, < 5s for 200 nodes - **Label Readability**: Zero overlapping labels in settled state - **Modal Load Time**: < 500ms for RDF preview ### Technical Metrics - **Test Coverage**: > 90% for graph interaction code - **Bundle Size**: < 500 KB added for D3 and graph utilities - **Memory Usage**: < 50 MB for graph with 200 nodes - **Frame Rate**: 60 FPS during interactions (no lag) --- ## πŸ”„ Maintenance Plan ### Regular Updates - **Weekly**: Check for D3.js security updates - **Monthly**: Review user feedback on graph interactions - **Quarterly**: Optimize physics simulation performance ### Known Limitations - **Large graphs** (>500 nodes): Consider implementing virtualization - **Complex ontologies**: May need hierarchical layout (not force-directed) - **Mobile**: Touch interactions may need refinement --- ## πŸ“ž Support ### Questions or Issues - Review example_ld source code: `/Users/kempersc/apps/example_ld/static/js/graph.js` - Consult D3.js documentation: https://d3js.org/ - Check Mermaid docs: https://mermaid.js.org/ - Ask in project Slack: #frontend-dev --- **Last Updated**: 2025-11-22 **Author**: AI Assistant (Claude) **Status**: βœ… COMPLETE - Ready for Implementation