# 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