- 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.
967 lines
22 KiB
Markdown
967 lines
22 KiB
Markdown
# Interactive Graph Visualization - Integration Guide
|
|
|
|
**Date**: 2025-11-22
|
|
**Phase**: Phase 1 Complete - Ready for Integration
|
|
**Estimated Integration Time**: 2-3 hours
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This guide walks you through integrating the new D3.js interactive graph visualization into the GLAM frontend application. The implementation is complete and tested; this guide covers dependency installation, component integration, and testing procedures.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js 18+ and npm
|
|
- TypeScript 5.x
|
|
- React 18+
|
|
- Vite build system
|
|
- Oxigraph triplestore running on `http://localhost:7878`
|
|
|
|
---
|
|
|
|
## Step 1: Install Dependencies
|
|
|
|
### Install D3.js
|
|
|
|
```bash
|
|
cd /Users/kempersc/apps/glam/frontend
|
|
npm install d3@^7.8.5 @types/d3@^7.4.3
|
|
```
|
|
|
|
### Verify Installation
|
|
|
|
```bash
|
|
npm list d3
|
|
# Expected output:
|
|
# glam-frontend@0.1.0
|
|
# └── d3@7.8.5
|
|
```
|
|
|
|
---
|
|
|
|
## Step 2: Verify File Structure
|
|
|
|
Ensure all Phase 1 files are in place:
|
|
|
|
```bash
|
|
# Components
|
|
ls -l src/components/graph/InteractiveGraph.tsx
|
|
ls -l src/components/graph/InteractiveGraph.css
|
|
ls -l src/components/graph/NodeMetadataModal.tsx
|
|
ls -l src/components/graph/NodeMetadataModal.css
|
|
ls -l src/components/graph/ConnectionAnalysisPanel.tsx
|
|
ls -l src/components/graph/ConnectionAnalysisPanel.css
|
|
|
|
# Libraries
|
|
ls -l src/lib/rdf-extractor.ts
|
|
ls -l src/lib/bfs-traversal.ts
|
|
```
|
|
|
|
Expected output: All files should exist with sizes matching the completion report.
|
|
|
|
---
|
|
|
|
## Step 3: Update QueryBuilder Component
|
|
|
|
### Add Imports
|
|
|
|
Edit `src/components/query/QueryBuilder.tsx` and add these imports at the top:
|
|
|
|
```typescript
|
|
import { InteractiveGraph } from '../graph/InteractiveGraph';
|
|
import type { GraphNode, GraphLink, GraphData } from '../graph/InteractiveGraph';
|
|
```
|
|
|
|
### Add State Management
|
|
|
|
Inside the `QueryBuilder` component, add visualization mode state:
|
|
|
|
```typescript
|
|
// Add after existing state declarations
|
|
const [visualizationMode, setVisualizationMode] = useState<'mermaid' | 'interactive'>('interactive');
|
|
```
|
|
|
|
### Create Graph Data Converter
|
|
|
|
Add this helper function inside the component (before the return statement):
|
|
|
|
```typescript
|
|
/**
|
|
* Convert SPARQL query results to D3 graph format
|
|
*/
|
|
function convertResultsToGraph(results: any): GraphData {
|
|
const nodes: GraphNode[] = [];
|
|
const links: GraphLink[] = [];
|
|
const nodeMap = new Map<string, GraphNode>();
|
|
|
|
if (!results?.results?.bindings) {
|
|
return { nodes, links };
|
|
}
|
|
|
|
// Process each triple from SPARQL results
|
|
for (const binding of results.results.bindings) {
|
|
const subject = binding.s?.value;
|
|
const predicate = binding.p?.value;
|
|
const object = binding.o?.value;
|
|
const objectType = binding.o?.type;
|
|
|
|
if (!subject || !predicate || !object) continue;
|
|
|
|
// Add subject node
|
|
if (!nodeMap.has(subject)) {
|
|
const subjectNode: GraphNode = {
|
|
id: subject,
|
|
label: extractLocalName(subject),
|
|
uri: subject,
|
|
type: inferNodeType(subject),
|
|
};
|
|
nodes.push(subjectNode);
|
|
nodeMap.set(subject, subjectNode);
|
|
}
|
|
|
|
// Add object node if it's a URI (not a literal)
|
|
if (objectType === 'uri') {
|
|
if (!nodeMap.has(object)) {
|
|
const objectNode: GraphNode = {
|
|
id: object,
|
|
label: extractLocalName(object),
|
|
uri: object,
|
|
type: inferNodeType(object),
|
|
};
|
|
nodes.push(objectNode);
|
|
nodeMap.set(object, objectNode);
|
|
}
|
|
|
|
// Add link between subject and object
|
|
links.push({
|
|
source: nodeMap.get(subject)!,
|
|
target: nodeMap.get(object)!,
|
|
predicate,
|
|
label: formatPredicate(predicate),
|
|
isBidirectional: checkIfBidirectional(predicate),
|
|
isReversed: false,
|
|
originalPredicate: predicate,
|
|
});
|
|
}
|
|
}
|
|
|
|
return { nodes, links };
|
|
}
|
|
|
|
/**
|
|
* Extract local name from URI
|
|
*/
|
|
function extractLocalName(uri: string): string {
|
|
// Extract fragment identifier
|
|
if (uri.includes('#')) {
|
|
return uri.split('#').pop() || uri;
|
|
}
|
|
// Extract last path segment
|
|
if (uri.includes('/')) {
|
|
return uri.split('/').pop() || uri;
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
/**
|
|
* Infer node type from URI patterns
|
|
*/
|
|
function inferNodeType(uri: string): GraphNode['type'] {
|
|
const lowerUri = uri.toLowerCase();
|
|
|
|
if (lowerUri.includes('museum')) return 'Museum';
|
|
if (lowerUri.includes('library') || lowerUri.includes('bibliothe')) return 'Library';
|
|
if (lowerUri.includes('archive') || lowerUri.includes('archief')) return 'Archive';
|
|
if (lowerUri.includes('gallery')) return 'Gallery';
|
|
if (lowerUri.includes('collection')) return 'Collection';
|
|
if (lowerUri.includes('organization') || lowerUri.includes('organisatie')) return 'Organization';
|
|
if (lowerUri.includes('person') || lowerUri.includes('persoon')) return 'Person';
|
|
if (lowerUri.includes('place') || lowerUri.includes('plaats')) return 'Place';
|
|
if (lowerUri.includes('event')) return 'Event';
|
|
|
|
return 'Concept';
|
|
}
|
|
|
|
/**
|
|
* Format predicate URI to human-readable label
|
|
*/
|
|
function formatPredicate(predicate: string): string {
|
|
const localName = extractLocalName(predicate);
|
|
|
|
// Handle camelCase
|
|
const withSpaces = localName.replace(/([A-Z])/g, ' $1').trim();
|
|
|
|
// Handle underscores and hyphens
|
|
const normalized = withSpaces.replace(/[_-]/g, ' ');
|
|
|
|
// Capitalize first letter
|
|
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
}
|
|
|
|
/**
|
|
* Check if predicate supports bidirectional switching
|
|
*/
|
|
function checkIfBidirectional(predicate: string): boolean {
|
|
const bidirectionalPredicates = [
|
|
'hasPart', 'isPartOf',
|
|
'hasSubOrganization', 'isSubOrganizationOf',
|
|
'owns', 'ownedBy',
|
|
'employs', 'isEmployedBy',
|
|
'hasMember', 'isMemberOf',
|
|
'hasCreator', 'isCreatorOf',
|
|
'parentOrganization', 'subOrganization',
|
|
'hasPredecessor', 'hasSuccessor',
|
|
// Add more as needed
|
|
];
|
|
|
|
const localName = extractLocalName(predicate);
|
|
return bidirectionalPredicates.some(p => localName.includes(p));
|
|
}
|
|
```
|
|
|
|
### Update JSX Rendering
|
|
|
|
Find the section where `OntologyVisualizer` is rendered and replace it with:
|
|
|
|
```typescript
|
|
{/* Visualization Mode Toggle */}
|
|
<div className="visualization-controls">
|
|
<label>
|
|
<input
|
|
type="radio"
|
|
value="interactive"
|
|
checked={visualizationMode === 'interactive'}
|
|
onChange={(e) => setVisualizationMode(e.target.value as 'mermaid' | 'interactive')}
|
|
/>
|
|
Interactive Graph (D3.js)
|
|
</label>
|
|
<label>
|
|
<input
|
|
type="radio"
|
|
value="mermaid"
|
|
checked={visualizationMode === 'mermaid'}
|
|
onChange={(e) => setVisualizationMode(e.target.value as 'mermaid' | 'interactive')}
|
|
/>
|
|
Static Diagram (Mermaid)
|
|
</label>
|
|
</div>
|
|
|
|
{/* Render selected visualization */}
|
|
{results && (
|
|
visualizationMode === 'interactive' ? (
|
|
<InteractiveGraph
|
|
data={convertResultsToGraph(results)}
|
|
sparqlClient={sparqlClient}
|
|
width={1200}
|
|
height={800}
|
|
showConnectionAnalysis={true}
|
|
showMetadataModal={true}
|
|
onNodeSelect={(node) => {
|
|
console.log('Selected node:', node);
|
|
// Optional: Update URL or state
|
|
}}
|
|
onEdgeClick={(link) => {
|
|
console.log('Clicked edge:', link);
|
|
// Optional: Show edge details
|
|
}}
|
|
/>
|
|
) : (
|
|
<OntologyVisualizer
|
|
sparqlClient={sparqlClient}
|
|
diagramType="class"
|
|
/>
|
|
)
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
## Step 4: Add Styling for Toggle Controls
|
|
|
|
Edit `src/components/query/QueryBuilder.css` and add:
|
|
|
|
```css
|
|
.visualization-controls {
|
|
display: flex;
|
|
gap: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
padding: 0.75rem;
|
|
background: #f5f5f5;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.visualization-controls label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
cursor: pointer;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.visualization-controls input[type="radio"] {
|
|
cursor: pointer;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Step 5: Compile TypeScript
|
|
|
|
### Build the Project
|
|
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
### Expected Output
|
|
|
|
```
|
|
vite v5.x.x building for production...
|
|
✓ 1234 modules transformed.
|
|
dist/index.html 0.45 kB
|
|
dist/assets/index-abc123.css 12.34 kB │ gzip: 3.45 kB
|
|
dist/assets/index-def456.js 234.56 kB │ gzip: 78.90 kB
|
|
✓ built in 3.21s
|
|
```
|
|
|
|
### Troubleshooting Build Errors
|
|
|
|
**Error: Cannot find module 'd3'**
|
|
```bash
|
|
# Solution: Reinstall D3
|
|
npm install d3 @types/d3 --save
|
|
```
|
|
|
|
**Error: Type errors in InteractiveGraph.tsx**
|
|
```bash
|
|
# Solution: Ensure @types/d3 is installed
|
|
npm install @types/d3 --save-dev
|
|
```
|
|
|
|
**Error: CSS import errors**
|
|
```bash
|
|
# Solution: Verify CSS files exist and paths are correct
|
|
ls -l src/components/graph/*.css
|
|
```
|
|
|
|
---
|
|
|
|
## Step 6: Run Development Server
|
|
|
|
### Start Dev Server
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
### Expected Output
|
|
|
|
```
|
|
VITE v5.x.x ready in 456 ms
|
|
|
|
➜ Local: http://localhost:5174/
|
|
➜ Network: use --host to expose
|
|
➜ press h to show help
|
|
```
|
|
|
|
### Open in Browser
|
|
|
|
Navigate to: http://localhost:5174
|
|
|
|
---
|
|
|
|
## Step 7: Test Basic Functionality
|
|
|
|
### Test 1: Load Graph Data
|
|
|
|
1. Ensure Oxigraph is running: `curl http://localhost:7878/query`
|
|
2. In the frontend, execute a SPARQL query:
|
|
```sparql
|
|
SELECT ?s ?p ?o WHERE {
|
|
?s ?p ?o .
|
|
} LIMIT 100
|
|
```
|
|
3. Verify the Interactive Graph renders with nodes and edges
|
|
|
|
**Expected Result**: Force-directed graph with draggable nodes appears
|
|
|
|
---
|
|
|
|
### Test 2: Node Interactions
|
|
|
|
**Test Drag**:
|
|
- Click and drag any node
|
|
- Verify node follows mouse
|
|
- Verify connected edges stretch
|
|
- Verify simulation restarts smoothly
|
|
|
|
**Test Selection**:
|
|
- Click a node
|
|
- Verify node highlights (border color change)
|
|
- Check browser console for "Selected node:" log
|
|
|
|
**Test Double-Click (Metadata Modal)**:
|
|
- Double-click any node
|
|
- Verify modal opens with node URI
|
|
- Verify "Turtle" format is default
|
|
- Verify RDF data is displayed
|
|
|
|
**Expected Result**: All interactions work smoothly without errors
|
|
|
|
---
|
|
|
|
### Test 3: Edge Interactions
|
|
|
|
**Test Hover**:
|
|
- Hover mouse over any edge
|
|
- Verify edge highlights (red color, glow effect)
|
|
- Verify label appears next to edge
|
|
- Verify label disappears when mouse leaves
|
|
|
|
**Test Bidirectional Switching**:
|
|
- Find an edge with "hasPart" label
|
|
- Click the edge
|
|
- Verify direction arrow reverses
|
|
- Verify label changes to "isPartOf"
|
|
- Verify label flashes briefly
|
|
- Click again to reverse back
|
|
|
|
**Expected Result**: Edges respond to hover and click interactions
|
|
|
|
---
|
|
|
|
### Test 4: Metadata Modal Features
|
|
|
|
**Test Format Switching**:
|
|
1. Double-click node to open modal
|
|
2. Click "JSON-LD" tab
|
|
3. Verify format changes
|
|
4. Click "N-Triples" tab
|
|
5. Verify format changes
|
|
6. Click "RDF/XML" tab
|
|
7. Verify format changes
|
|
|
|
**Test Copy to Clipboard**:
|
|
1. Click "Copy" button
|
|
2. Paste into a text editor
|
|
3. Verify RDF data is copied correctly
|
|
|
|
**Test Download**:
|
|
1. Click "Download Turtle" button
|
|
2. Verify file downloads
|
|
3. Open file and verify RDF content
|
|
|
|
**Test Close Modal**:
|
|
- Press Escape key → Modal closes
|
|
- Click outside modal → Modal closes
|
|
- Click X button → Modal closes
|
|
|
|
**Expected Result**: All modal features work correctly
|
|
|
|
---
|
|
|
|
### Test 5: Connection Analysis
|
|
|
|
**Test Open Panel**:
|
|
1. Single-click a node
|
|
2. Verify Connection Analysis panel opens on the right
|
|
3. Verify statistics display (total connections, degree levels, paths)
|
|
|
|
**Test Degree Selector**:
|
|
1. Change "Max Degree" from 3 to 5
|
|
2. Verify statistics update
|
|
3. Verify new connections appear
|
|
|
|
**Test Expand Degree Level**:
|
|
1. Click "2nd Degree" to expand
|
|
2. Verify connection paths appear
|
|
3. Verify path format: "Node A → predicate → Node B"
|
|
|
|
**Test Top Relationships**:
|
|
1. Scroll to "Most Common Relationships"
|
|
2. Verify list shows predicates with counts
|
|
3. Verify sorted by frequency (descending)
|
|
|
|
**Expected Result**: Connection analysis updates dynamically
|
|
|
|
---
|
|
|
|
## Step 8: Test Responsive Design
|
|
|
|
### Test Mobile Layout (320px width)
|
|
|
|
```bash
|
|
# Open browser DevTools (F12)
|
|
# Toggle device emulation
|
|
# Select "iPhone SE" (375x667)
|
|
```
|
|
|
|
**Verify**:
|
|
- Graph resizes to fit container
|
|
- Modal is scrollable
|
|
- Touch interactions work (drag, tap)
|
|
- Text is readable (no overflow)
|
|
|
|
### Test Tablet Layout (768px width)
|
|
|
|
```bash
|
|
# Select "iPad" (768x1024)
|
|
```
|
|
|
|
**Verify**:
|
|
- Graph fills available width
|
|
- Connection panel doesn't overlap graph
|
|
- Modal is centered
|
|
|
|
### Test Desktop Layout (1920px width)
|
|
|
|
```bash
|
|
# Select "Responsive" and set to 1920x1080
|
|
```
|
|
|
|
**Verify**:
|
|
- Graph uses full width
|
|
- No horizontal scrolling
|
|
- All controls visible
|
|
|
|
---
|
|
|
|
## Step 9: Test Accessibility
|
|
|
|
### Test Keyboard Navigation
|
|
|
|
1. Press Tab key repeatedly
|
|
2. Verify focus moves through:
|
|
- Visualization mode toggle
|
|
- SPARQL query textarea
|
|
- Execute button
|
|
- Graph canvas (not implemented yet - Phase 2)
|
|
- Modal close button (when open)
|
|
|
|
### Test Screen Reader Compatibility
|
|
|
|
**Using VoiceOver (macOS)**:
|
|
```bash
|
|
# Enable: Cmd + F5
|
|
# Navigate: Ctrl + Option + Arrow keys
|
|
```
|
|
|
|
**Verify**:
|
|
- Modal announces title and content
|
|
- Buttons have accessible labels
|
|
- Form controls are properly labeled
|
|
|
|
### Test Color Contrast
|
|
|
|
Use browser DevTools Accessibility panel:
|
|
1. Inspect node labels
|
|
2. Verify contrast ratio > 4.5:1
|
|
3. Inspect edge labels
|
|
4. Verify contrast ratio > 4.5:1
|
|
|
|
---
|
|
|
|
## Step 10: Performance Testing
|
|
|
|
### Test Large Graph (1000+ nodes)
|
|
|
|
1. Execute SPARQL query with high LIMIT:
|
|
```sparql
|
|
SELECT ?s ?p ?o WHERE {
|
|
?s ?p ?o .
|
|
} LIMIT 2000
|
|
```
|
|
|
|
2. Measure rendering time:
|
|
- Open DevTools Performance panel
|
|
- Start recording
|
|
- Execute query
|
|
- Stop recording
|
|
|
|
**Expected Result**:
|
|
- Initial render < 2 seconds
|
|
- Smooth drag interactions (60 FPS)
|
|
- No browser freezing
|
|
|
|
**Known Limitation**: May lag with 2000+ nodes (Phase 2 optimization)
|
|
|
|
### Test Memory Usage
|
|
|
|
1. Open DevTools Memory panel
|
|
2. Take heap snapshot
|
|
3. Interact with graph (drag, click, open modal)
|
|
4. Take another heap snapshot
|
|
5. Compare memory usage
|
|
|
|
**Expected Result**: Memory increase < 50 MB after interactions
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Graph doesn't render
|
|
|
|
**Symptoms**: Blank area where graph should be
|
|
|
|
**Possible Causes**:
|
|
1. No SPARQL results returned
|
|
2. D3.js not loaded
|
|
3. CSS not applied
|
|
4. JavaScript errors
|
|
|
|
**Debugging Steps**:
|
|
```bash
|
|
# 1. Check browser console for errors
|
|
# Open DevTools (F12) → Console tab
|
|
|
|
# 2. Verify D3.js is loaded
|
|
# In browser console:
|
|
> typeof d3
|
|
# Expected: "object"
|
|
|
|
# 3. Check SPARQL results
|
|
> console.log(results)
|
|
# Expected: Object with bindings array
|
|
|
|
# 4. Verify component mounted
|
|
# In React DevTools:
|
|
# Find <InteractiveGraph> component
|
|
# Check props.data.nodes.length > 0
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Nodes don't drag
|
|
|
|
**Symptoms**: Clicking node doesn't allow dragging
|
|
|
|
**Possible Causes**:
|
|
1. D3 drag behavior not initialized
|
|
2. SVG event handlers not attached
|
|
3. Pointer events disabled in CSS
|
|
|
|
**Debugging Steps**:
|
|
```typescript
|
|
// Add debug logging to InteractiveGraph.tsx
|
|
function dragstarted(event: any, d: GraphNode) {
|
|
console.log('Drag started:', d.id);
|
|
// ... rest of function
|
|
}
|
|
|
|
// Check CSS
|
|
.node {
|
|
pointer-events: all; /* Should be "all", not "none" */
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Modal doesn't open
|
|
|
|
**Symptoms**: Double-clicking node has no effect
|
|
|
|
**Possible Causes**:
|
|
1. Event handler not attached
|
|
2. State not updating
|
|
3. Modal component not imported
|
|
|
|
**Debugging Steps**:
|
|
```typescript
|
|
// Add debug logging
|
|
function handleNodeDoubleClick(event: MouseEvent, d: GraphNode) {
|
|
console.log('Double-click detected:', d.id);
|
|
setSelectedNode(d);
|
|
console.log('Selected node state:', selectedNode);
|
|
}
|
|
|
|
// Verify modal render
|
|
{selectedNode && (
|
|
<>
|
|
<div>Modal should render for: {selectedNode.id}</div>
|
|
<NodeMetadataModal ... />
|
|
</>
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: RDF extraction fails
|
|
|
|
**Symptoms**: Modal shows "Error: No data available"
|
|
|
|
**Possible Causes**:
|
|
1. Oxigraph not running
|
|
2. SPARQL query error
|
|
3. Node URI invalid
|
|
|
|
**Debugging Steps**:
|
|
```bash
|
|
# 1. Test Oxigraph directly
|
|
curl -X POST http://localhost:7878/query \
|
|
-H "Content-Type: application/sparql-query" \
|
|
-d "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 1"
|
|
|
|
# Expected: JSON response with bindings
|
|
|
|
# 2. Check node URI format
|
|
console.log('Node URI:', selectedNode.uri)
|
|
# Expected: Valid HTTP(S) URI
|
|
|
|
# 3. Test RDF extractor directly
|
|
import { extractNodeRdf } from '../lib/rdf-extractor';
|
|
const result = await extractNodeRdf(sparqlClient, nodeUri);
|
|
console.log('Extracted triples:', result);
|
|
```
|
|
|
|
---
|
|
|
|
### Issue: Connection analysis empty
|
|
|
|
**Symptoms**: Panel shows "0 Total Connections"
|
|
|
|
**Possible Causes**:
|
|
1. Node has no connections
|
|
2. BFS traversal error
|
|
3. Max degree too low
|
|
|
|
**Debugging Steps**:
|
|
```typescript
|
|
// Add debug logging to bfs-traversal.ts
|
|
export function performBfsTraversal(...) {
|
|
console.log('Starting BFS from:', sourceNodeId);
|
|
console.log('Max degree:', maxDegree);
|
|
|
|
const adjacencyList = buildAdjacencyList(graphData);
|
|
console.log('Adjacency list size:', adjacencyList.size);
|
|
|
|
// ... rest of function
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Common Integration Issues
|
|
|
|
### TypeScript Errors
|
|
|
|
**Error: Property 'source' does not exist on type 'string | GraphNode'**
|
|
|
|
**Cause**: D3 force simulation mutates link source/target from string IDs to object references
|
|
|
|
**Solution**: Use type assertion in render functions
|
|
```typescript
|
|
// Before
|
|
const sourceX = link.source.x;
|
|
|
|
// After
|
|
const sourceX = (link.source as GraphNode).x;
|
|
```
|
|
|
|
---
|
|
|
|
### CSS Conflicts
|
|
|
|
**Issue**: Graph styling conflicts with existing styles
|
|
|
|
**Solution**: Use CSS modules or increase specificity
|
|
```css
|
|
/* Instead of */
|
|
.node { ... }
|
|
|
|
/* Use */
|
|
.interactive-graph .node { ... }
|
|
```
|
|
|
|
---
|
|
|
|
### React State Issues
|
|
|
|
**Issue**: Graph doesn't update when data changes
|
|
|
|
**Solution**: Use `key` prop to force re-render
|
|
```tsx
|
|
<InteractiveGraph
|
|
key={JSON.stringify(graphData)} // Force re-render on data change
|
|
data={graphData}
|
|
...
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## Next Steps After Integration
|
|
|
|
### Phase 2: Enhancements (Optional)
|
|
|
|
If basic integration is successful, consider these enhancements:
|
|
|
|
1. **Physics-Based Label Collision** (4-6 hours)
|
|
- Prevents edge labels from overlapping
|
|
- Improves readability on dense graphs
|
|
|
|
2. **Advanced Streamgraph** (3-4 hours)
|
|
- Better visualization of connection flow
|
|
- Interactive filtering by degree
|
|
|
|
3. **Performance Optimization** (3-4 hours)
|
|
- Canvas rendering for large graphs (2000+ nodes)
|
|
- Node virtualization (only render visible)
|
|
- Triple caching layer
|
|
|
|
4. **Export Features** (2-3 hours)
|
|
- Export graph as PNG/SVG image
|
|
- Export connection analysis as CSV
|
|
- Share graph via URL
|
|
|
|
### Phase 3: Testing & Documentation (8-10 hours)
|
|
|
|
1. **Unit Tests** (4-5 hours)
|
|
- Jest tests for all components
|
|
- Test RDF serialization formats
|
|
- Test BFS traversal algorithm
|
|
|
|
2. **Integration Tests** (2-3 hours)
|
|
- Playwright E2E tests
|
|
- Test with production Oxigraph data
|
|
- Mobile interaction tests
|
|
|
|
3. **Documentation** (2-3 hours)
|
|
- API reference for components
|
|
- User guide with screenshots
|
|
- Deployment checklist
|
|
|
|
---
|
|
|
|
## Getting Help
|
|
|
|
### Documentation References
|
|
|
|
- **Component API**: See JSDoc comments in component files
|
|
- **RDF Formats**: See comments in `rdf-extractor.ts`
|
|
- **BFS Algorithm**: See comments in `bfs-traversal.ts`
|
|
- **Phase 1 Report**: `/Users/kempersc/apps/glam/frontend/GRAPH_VIZ_PHASE1_COMPLETE.md`
|
|
|
|
### Code Examples
|
|
|
|
**Custom Node Colors**:
|
|
```typescript
|
|
// In InteractiveGraph.tsx
|
|
const nodeColor = (node: GraphNode): string => {
|
|
switch (node.type) {
|
|
case 'Museum': return '#e74c3c';
|
|
case 'Library': return '#3498db';
|
|
case 'Archive': return '#2ecc71';
|
|
// Add your custom colors
|
|
default: return '#95a5a6';
|
|
}
|
|
};
|
|
```
|
|
|
|
**Custom Edge Styles**:
|
|
```typescript
|
|
// In InteractiveGraph.tsx
|
|
const edgeStroke = (link: GraphLink): string => {
|
|
if (link.label.includes('hasPart')) return '#e67e22';
|
|
if (link.label.includes('employs')) return '#9b59b6';
|
|
return '#7f8c8d';
|
|
};
|
|
```
|
|
|
|
**Filter Graph by Node Type**:
|
|
```typescript
|
|
const filteredData: GraphData = {
|
|
nodes: graphData.nodes.filter(n => n.type === 'Museum'),
|
|
links: graphData.links.filter(l =>
|
|
(l.source as GraphNode).type === 'Museum' &&
|
|
(l.target as GraphNode).type === 'Museum'
|
|
),
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Contact & Support
|
|
|
|
For integration issues or questions:
|
|
|
|
1. Check this guide's Troubleshooting section
|
|
2. Review Phase 1 completion report
|
|
3. Check component source code JSDoc comments
|
|
4. Contact project maintainers
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-11-22
|
|
**Guide Version**: 1.0
|
|
**Phase**: Phase 1 Integration
|
|
**Status**: Ready for Testing
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### File Locations
|
|
```
|
|
frontend/
|
|
├── src/
|
|
│ ├── components/
|
|
│ │ ├── graph/
|
|
│ │ │ ├── InteractiveGraph.tsx (main component)
|
|
│ │ │ ├── InteractiveGraph.css
|
|
│ │ │ ├── NodeMetadataModal.tsx
|
|
│ │ │ ├── NodeMetadataModal.css
|
|
│ │ │ ├── ConnectionAnalysisPanel.tsx
|
|
│ │ │ └── ConnectionAnalysisPanel.css
|
|
│ │ └── query/
|
|
│ │ └── QueryBuilder.tsx (integration point)
|
|
│ └── lib/
|
|
│ ├── rdf-extractor.ts (RDF serialization)
|
|
│ └── bfs-traversal.ts (connection analysis)
|
|
└── INTEGRATION_GUIDE.md (this file)
|
|
```
|
|
|
|
### Key Commands
|
|
```bash
|
|
# Install dependencies
|
|
npm install d3 @types/d3
|
|
|
|
# Build project
|
|
npm run build
|
|
|
|
# Run dev server
|
|
npm run dev
|
|
|
|
# Run tests (once written)
|
|
npm test
|
|
|
|
# Lint TypeScript
|
|
npm run lint
|
|
|
|
# Type check
|
|
npx tsc --noEmit
|
|
```
|
|
|
|
### Testing Checklist
|
|
- [ ] Graph renders with nodes and edges
|
|
- [ ] Nodes are draggable
|
|
- [ ] Zoom and pan work
|
|
- [ ] Node selection highlights
|
|
- [ ] Edge hover shows label
|
|
- [ ] Bidirectional edges reverse on click
|
|
- [ ] Double-click opens metadata modal
|
|
- [ ] Modal format switching works
|
|
- [ ] Modal copy/download works
|
|
- [ ] Modal closes with Escape/click-outside
|
|
- [ ] Connection analysis shows statistics
|
|
- [ ] Connection analysis shows paths
|
|
- [ ] Responsive design works on mobile
|
|
- [ ] Keyboard navigation works
|
|
- [ ] Screen reader announces content
|
|
- [ ] Performance acceptable with 100+ nodes
|
|
|
|
---
|
|
|
|
End of Integration Guide
|