- 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.
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:
- Bidirectional edge switching - Click edges to reverse direction
- Node pop-up metadata - Double-click nodes for detailed modal
- Edge highlighting - Hover to see edge labels and highlighting
- Label collision avoidance - Intelligent positioning of edge labels
- Multi-degree connection analysis - Streamgraph showing 1st, 2nd, 3rd degree connections
- RDF data extraction - Click nodes to extract RDF triples in multiple formats
- 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:
hasCreator↔isCreatorOfhasLocation↔isLocationOfhasCollection↔isPartOfCollectionhasCustodian↔isCustodianOf
Implementation Plan:
- Create
HERITAGE_BIDIRECTIONAL_MAPPINGSfor GLAM ontology - Add click handler to edges in
OntologyVisualizer.tsx - Update Mermaid diagram syntax to show direction changes
- 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:
- Create React modal component for node details
- Double-click handler on nodes
- Fetch full institution record from SPARQL endpoint
- Display metadata in organized sections
- Include SPARQL query to retrieve this node's data
- 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:
- Add hidden label layer in Mermaid diagram
- Implement hover detection with D3 overlay
- Create arrow marker definitions for each property type
- 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:
- Implement bounding box tracking for Mermaid edge labels
- Add physics simulation for label positioning
- Use D3 force simulation or custom collision detection
- 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:
- Implement BFS graph traversal algorithm
- Create streamgraph visualization component (D3.js or Chart.js)
- Add degree level filtering UI
- Integrate with node click events
- 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:
- Add SPARQL DESCRIBE query for selected node
- Create format conversion utilities (SPARQL JSON → Turtle/JSON-LD/N-Triples)
- Implement syntax highlighting (Prism.js or Monaco)
- Add RDF preview panel to node details modal
- 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:
- Store CSV provenance in RDF as PROV-O triples
- SPARQL query to retrieve provenance for node
- Display CSV row in modal with field highlighting
- Add "View Full Record in CSV" link
- 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) - ✅
reactandreact-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