glam/frontend/GRAPH_VIZ_FEATURE_CHECKLIST.md
kempersc 2761857b0d Add scripts for converting OWL/Turtle ontology to Mermaid and PlantUML diagrams
- Implemented `owl_to_mermaid.py` to convert OWL/Turtle files into Mermaid class diagrams.
- Implemented `owl_to_plantuml.py` to convert OWL/Turtle files into PlantUML class diagrams.
- Added two new PlantUML files for custodian multi-aspect diagrams.
2025-11-22 23:01:13 +01:00

32 KiB

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:

// 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:

  • hasCreatorisCreatorOf
  • hasLocationisLocationOf
  • hasCollectionisPartOfCollection
  • hasCustodianisCustodianOf

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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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

{
  "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:

// 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:

// 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:

// tests/integration/NodeModalFlow.test.tsx
describe('NodeModalFlow', () => {
  it('should open modal on double-click and display RDF', async () => {
    const { container } = render(<OntologyVisualizer {...props} />);
    
    // 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:

// tests/visual/GraphVisualization.visual.test.tsx
describe('GraphVisualization', () => {
  it('should render graph with correct node positions', async () => {
    const { container } = render(<OntologyVisualizer {...props} />);
    
    // 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):

# 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):

# 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