Skip to content

ADR-035: Explorer Methods, Uses, and Capabilities

Status: Proposed Date: 2025-10-17 Last Updated: 2025-10-17 Deciders: Development Team Related: ADR-034 (Graph Visualization Architecture), ADR-037 (Human-Guided Graph Editing)

Overview

Building a graph visualization isn't enough—you need to make it usable. ADR-034 established the architecture for explorers, but left crucial questions unanswered: How do users navigate between concepts? What happens when they click a node? How do they follow relationships without losing their place?

Graph exploration has unique interaction challenges. Unlike browsing files in folders, you can arrive at the same concept through multiple semantic paths. You need to track where you came from, highlight your current focus, and provide intuitive ways to traverse relationships. Standard UI patterns break down when concepts exist in multiple contexts simultaneously.

This ADR documents the specific explorer types, their interaction patterns, and navigation enhancements like "You Are Here" highlighting, breadcrumb trails, and context menus. It covers how users move through the graph (clicking, right-clicking, keyboard navigation), how the system maintains visual continuity, and how to implement these patterns using D3.js and DOM manipulation. These details transform static visualizations into fluid exploration tools.


Context

ADR-034 establishes the core architecture for the graph visualization application. This ADR documents the specific explorer types, their use cases, interaction patterns, and planned enhancements for navigating and understanding the knowledge graph.

Explorers are specialized visualization modes optimized for different analysis tasks. Each explorer type serves distinct user needs - from discovering conceptual neighborhoods to comparing ontologies or analyzing temporal evolution.

Explorer Types

1. Force-Directed Graph (2D/3D)

Use Case: Explore conceptual neighborhoods and discover clustering patterns

Best For: - Discovering unexpected connections between concepts - Understanding relationship density in graph regions - Identifying hub concepts (high-degree nodes) - Exploring concept neighborhoods interactively

Libraries: - 2D: D3-force, react-force-graph-2d - 3D: Three.js via react-force-graph-3d

Features: - Physics simulation (attraction/repulsion forces) - Cluster highlighting - Relationship filtering by type - Node sizing by degree/betweenness centrality - Color coding by ontology, terms count, vector similarity, or edge phenotype - Zoom/pan/rotate controls (3D) - Dynamic force parameters (charge, link distance, gravity)

Current Interactions: - Click node → Focus subgraph on that concept (re-centers exploration) - Hover node → Highlight neighbors and connecting edges - Drag node → Reposition and pin to location - Scroll/pinch → Zoom in/out - Click+drag background → Pan viewport

Planned Interactions (ADR-035 Enhancements): - Double-click node → Expand neighbors into existing graph (additive) - Right-click node → Context menu (expand, find path to, view details, copy ID) - Ctrl+Click node → Add to selection set - Shift+Click two nodes → Find shortest path between them - Keyboard arrows → Pan viewport - +/- keys → Zoom - Escape → Clear selection

// Example: Force-directed graph component
interface ForceGraphProps {
  nodes: GraphNode[];
  links: GraphLink[];
  focusNodeId?: string;
  colorBy: 'ontology' | 'degree' | 'centrality';
  physics: {
    charge: number;
    linkDistance: number;
    gravity: number;
  };
}

const ForceGraph: React.FC<ForceGraphProps> = ({
  nodes, links, focusNodeId, colorBy, physics
}) => {
  // D3 force simulation
  const simulation = useD3ForceSimulation(nodes, links, physics);

  // Highlight neighbors on hover
  const [hoveredNode, setHoveredNode] = useState<string | null>(null);
  const neighbors = useNeighbors(hoveredNode, links);

  return (
    <svg viewBox="0 0 1000 1000">
      <Links links={links} highlighted={neighbors} />
      <Nodes
        nodes={nodes}
        colorBy={colorBy}
        onHover={setHoveredNode}
        focused={focusNodeId}
      />
    </svg>
  );
};

2. Hierarchical Tree Visualization

Use Case: Explore taxonomies and containment relationships

Best For: - Understanding IS-A and PART-OF hierarchies - Exploring classification systems - Analyzing ontology structure - Navigating nested concepts

Libraries: - D3-hierarchy (tree, cluster, partition) - react-d3-tree

Layouts: - Radial tree (circular layout) - Tidy tree (traditional top-down) - Treemap (nested rectangles) - Sunburst (radial partitioning)

Features: - Collapse/expand branches - Breadcrumb navigation - Leaf node search - Depth limiting - Ancestor highlighting - Subtree statistics

Interactions: - Click node → Expand/collapse children - Double-click → Focus on subtree - Breadcrumb click → Navigate to ancestor

// Example: Hierarchical tree
interface TreeNode {
  id: string;
  label: string;
  children?: TreeNode[];
  depth: number;
}

const HierarchyTree: React.FC<{ root: TreeNode }> = ({ root }) => {
  const [collapsed, setCollapsed] = useState<Set<string>>(new Set());

  const hierarchy = useMemo(() =>
    d3.hierarchy(root)
      .sort((a, b) => a.data.label.localeCompare(b.data.label))
  , [root]);

  const treeLayout = d3.tree<TreeNode>()
    .size([1000, 800])
    .separation((a, b) => a.parent === b.parent ? 1 : 2);

  return (
    <svg>
      <TreeLinks tree={treeLayout(hierarchy)} />
      <TreeNodes
        tree={treeLayout(hierarchy)}
        collapsed={collapsed}
        onToggle={(id) => toggleCollapse(id, collapsed, setCollapsed)}
      />
    </svg>
  );
};

3. Sankey Diagram

Use Case: Visualize knowledge flow between ontologies

Best For: - Understanding how concepts flow between ontologies - Planning ontology merges - Tracing evidence from sources to concepts - Analyzing relationship type distribution

Libraries: - D3-sankey - react-vis

Features: - Concept migration paths - Ontology merging preview - Source attribution flow - Relationship type distribution - Flow volume indicators

Example Use: - Show which concepts from "Research Papers" ontology → "Product Documentation" - Trace evidence flow from source documents → concept instances - Visualize how IMPLIES relationships connect concept groups

4. Matrix/Heatmap View

Use Case: Compare relationship patterns across concepts

Best For: - Identifying relationship patterns - Finding missing connections - Analyzing relationship symmetry - Comparing concept pairs

Libraries: - D3-scale, D3-axis - visx (reusable chart components)

Features: - Adjacency matrix (concept → concept) - Relationship type heatmap - Temporal evolution grid - Correlation analysis - Sortable rows/columns

Example:

Concept A Concept B Concept C
Concept A - IMPLIES SUPPORTS
Concept B IMPLIES - CONTRADICTS
Concept C SUPPORTS CONTRADICTS -

5. Timeline Visualization

Use Case: Explore concept evolution over time

Best For: - Understanding graph growth patterns - Analyzing ingestion history - Identifying concept emergence - Tracking relationship formation

Libraries: - D3-time-scale - vis-timeline

Features: - Concept creation timeline - Relationship formation events - Ingestion batch markers - Ontology version history - Growth rate metrics - Temporal filtering

Interactions: - Scrub timeline → View graph at specific date - Click event → Show details - Zoom timeline → Adjust granularity - Play button → Animate growth

6. Concept Density Map

Use Case: Identify knowledge-rich areas and gaps

Best For: - Finding under-documented regions - Identifying concept clusters - Planning curation efforts - Visualizing coverage

Libraries: - D3-contour (for density) - Deck.gl (hexagonal binning)

Features: - Heatmap of concept clusters - Ontology coverage overlay - Sparse area highlighting - Interactive drill-down - Density metrics

Query Workbenches

Workbench 1: Visual Query Builder

Concept: No-code graph pattern construction

Use Case: Build complex graph queries without writing openCypher

┌─────────────────────────────────────────┐
│  Visual Query Builder                   │
│                                         │
│      IMPLIES                            │
│   ┌──────┐        ┌──────┐              │
│   │Node1 │───────>│Node2 │              │
│   └───┬──┘        └──────┘              │
│       │                                 │
│       │ PART_OF                         │
│       │                                 │
│       ▼                                 │
│   ┌──────┐                              │
│   │Node3 │                              │
│   └──────┘                              │
│                                         │
│  [Add Node] [Add Edge] [Run Query]      │
│                                         │
│  Generated openCypher:                  │
│  MATCH (n1:Concept)-[:IMPLIES]->        │
│        (n2:Concept)                     │
│  WHERE (n1)-[:PART_OF]->(:Concept)      │
│  RETURN n1, n2                          │
└─────────────────────────────────────────┘

Features: - Drag-and-drop nodes and edges - Filter palette (relationship types) - Property constraints (label, ontology) - Path length controls - Live query preview - Save/load query templates - Export as openCypher

Workbench 2: Path Explorer

Concept: Find and visualize paths between concepts

┌─────────────────────────────────────────┐
│  Path Explorer                          │
│                                         │
│  From: [AI Safety       ▼]              │
│  To:   [Regulatory Framework ▼]         │
│                                         │
│  Max Hops: [5]  Algorithm: [Shortest ▼] │
│                                         │
│  [Find Paths]                           │
│                                         │
│  Results: 3 paths found                 │
│                                         │
│                                         │
│   Path 1 (3 hops): 89% confidence       │
│   AI Safety → Ethics → Policy →         │
│   Regulatory Framework                  │
│                                         │
└─────────────────────────────────────────┘

Features: - Concept auto-complete - Multiple path algorithms (shortest, all simple, weighted) - Confidence scoring - Path comparison side-by-side - Export to citation format - Highlight paths in main graph

Workbench 3: Neighborhood Inspector

Concept: Deep-dive into concept surroundings

┌─────────────────────────────────────────┐
│  Neighborhood Inspector                 │
│                                         │
│  Focus: [Machine Learning]              │
│                                         │
│  Depth: [2]  Relationship: [All ▼]      │
│                                         │
│                                         │
│       [Neural Networks]                 │
│              │ PART_OF                  │
│              ▼                          │
│       [Machine Learning] ◄────┐         │
│              │                │         │
│       ┌──────┴──────┐         │         │
│       │             │         │         │
│    REQUIRES      ENABLES   IMPLIES      │
│       │             │         │         │
│       ▼             ▼         │         │
│    [Data]    [Automation]  ───┘         │
│                                         │
│                                         │
│  Stats:                                 │
│  • 12 neighbors at depth 1              │
│  • 47 neighbors at depth 2              │
│  • Avg relationship strength: 0.82      │
└─────────────────────────────────────────┘

Features: - Expandable radius (1-5 hops) - Relationship type filtering - Degree distribution chart - Orphan concept highlighting - Export subgraph - Statistics overlay

Workbench 4: Ontology Comparator

Concept: Side-by-side ontology analysis

┌────────────────────────────────────────────────────────┐
│  Ontology Comparator                                   │
│                                                        │
│  Left: [Research Papers ▼]  Right: [Blog Posts ▼]      │
│                                                        │
│                                                        │
│    Unique: 45              Unique: 32                  │
│    Shared: 23    ◄═══►    Shared: 23                   │
│    Total: 68               Total: 55                   │
│                                                        │
│                                                        │
│  Shared Concepts:                                      │
│  • Neural Networks (95% similar)                       │
│  • Deep Learning (88% similar)                         │
│  • Transfer Learning (76% similar)                     │
│                                                        │
│  [Merge Preview] [Export Diff]                         │
└────────────────────────────────────────────────────────┘

Features: - Venn diagram visualization - Concept similarity scoring - Merge impact preview - Relationship overlap analysis - Diff export

Workbench 5: Temporal Evolution Viewer

Concept: Watch graph grow over time

┌─────────────────────────────────────────┐
│  Temporal Evolution Viewer              │
│                                         │
│  Timeline: [◄] ████▓████████████        │
│            Jan    Mar     Jun           │
│                                         │
│                                         │
│         [Graph at Mar 15]               │
│                                         │
│    ▸ New concepts: +12                  │
│    ▸ New relationships: +34             │
│    ▸ Merged concepts: 3                 │
│                                         │
│                                         │
│  Growth Rate: +4.2 concepts/week        │
│  [Play Animation] [Export GIF]          │
└─────────────────────────────────────────┘

Features: - Playback controls (play/pause/step) - Growth metrics overlay - Highlight new/modified nodes - Export as animated GIF/video

Universal Interaction Patterns

All explorers implement a consistent interaction model for navigating and exploring the graph. These patterns work across Force-Directed, Hierarchical, Timeline, and all other explorer types.

Interaction Modes

View Mode (ADR-035): Navigate and explore existing graph data - Query concepts, neighborhoods, and paths - Follow connections between concepts - Visualize relationships - Does NOT modify graph structure

Edit Mode (ADR-037): Modify graph structure with human guidance - Create new relationships between concepts - Invalidate/flag incorrect relationships - Add human justifications as evidence - See ADR-037: Human-Guided Graph Editing for details

Core Interaction Patterns

All explorers support these fundamental interactions:

Node Interactions: - Left-click node → Select/highlight node - Right-click node → Context menu (actions vary by mode and explorer) - Double-click node → Explorer-specific action (e.g., expand neighbors, focus subtree) - Hover node → Show tooltip with concept details - Drag node → Reposition (in physics-based explorers)

Edge Interactions: - Left-click edge → Select/highlight edge - Right-click edge → Context menu (view details, in edit mode: invalidate) - Hover edge → Show tooltip with relationship type and confidence

Empty Area Interactions: - Left-click empty area → Deselect all (or start region selection box in some explorers) - Right-click empty area → Context menu (e.g., "Add Concept", "Create Node" in edit mode) - Click+drag empty area → Pan viewport - Scroll/pinch → Zoom in/out

View Mode: Context Menu Actions

Node Right-Click Menu (View Mode): - Follow [Concept Name] → Query this concept and replace graph with results - Add [Concept Name] to Graph → Query this concept and merge results with existing graph - Find Path To... → Opens path finder dialog to find routes to another concept - View Details → Opens side panel with concept details, instances, evidence - Copy Concept ID → Copy concept_id to clipboard - Export Subgraph → Export neighborhood as JSON/GraphML - Hide from Graph → Temporarily hide this node from view

Edge Right-Click Menu (View Mode): - View Relationship Details → Show confidence, source documents, evidence instances - Find Similar Relationships → Query for other edges of this type - Hide Edge Type → Temporarily hide all edges of this relationship type

Empty Area Right-Click Menu (View Mode): - Zoom to Fit → Auto-zoom to show all visible nodes - Reset View → Return to initial graph state - Export Graph → Export current visualization as image or data

For Edit Mode context menu actions (create connections, invalidate relationships), see ADR-037: Human-Guided Graph Editing.

Follow Concept Capability

Status: Lost capability - needs restoration

Use Case: Navigate the graph by following concepts without retyping queries

Workflow: 1. User performs initial search (concept/neighborhood/path) → graph loads 2. User left-clicks a node → node highlights ("You Are Here") 3. User right-clicks highlighted node → context menu appears 4. User selects "Follow [Concept Name]" or "Add [Concept Name] to Graph" 5. System queries using concept's existing embedding (no recomputation needed) 6. New graph loads: - Follow: Replace graph with new query results (clean slate) - Add to Graph: Merge new results with existing nodes/edges (additive)

Key Implementation Details:

  1. No Embedding Recomputation: The concept already has embeddings stored in the database
  2. Respects Similarity Threshold: Uses current similarity threshold slider setting
  3. Direct API Query: Bypasses text → embedding flow, queries directly using concept data

API Call Pattern:

// User right-clicks concept node and selects "Follow"
async function followConcept(conceptId: string, mode: 'replace' | 'add') {
  // Fetch concept details including embedding
  const concept = await apiClient.get(`/query/concept/${conceptId}`);

  // Query using existing embedding (no need to generate new one)
  const results = await apiClient.post('/query/search', {
    embedding: concept.embedding,  // Use stored embedding directly
    similarity_threshold: currentThreshold,  // From UI slider
    limit: 50
  });

  if (mode === 'replace') {
    // Replace entire graph
    setGraphData(results);
  } else {
    // Merge with existing graph
    setGraphData(prev => ({
      nodes: mergeNodes(prev.nodes, results.nodes),
      links: mergeLinks(prev.links, results.links)
    }));
  }

  // Update "You Are Here" highlight
  setOriginNodeId(conceptId);
}

Context Menu Implementation:

interface ContextMenuProps {
  node: GraphNode;
  position: { x: number; y: number };
  onClose: () => void;
}

const NodeContextMenu: React.FC<ContextMenuProps> = ({ node, position, onClose }) => {
  return (
    <ContextMenu x={position.x} y={position.y}>
      <MenuItem
        icon={<ArrowRight />}
        onClick={() => {
          followConcept(node.concept_id, 'replace');
          onClose();
        }}
      >
        Follow "{node.label}"
      </MenuItem>

      <MenuItem
        icon={<Plus />}
        onClick={() => {
          followConcept(node.concept_id, 'add');
          onClose();
        }}
      >
        Add "{node.label}" to Graph
      </MenuItem>

      <Separator />

      <MenuItem icon={<Route />} onClick={() => openPathFinder(node)}>
        Find Path To...
      </MenuItem>

      <MenuItem icon={<Info />} onClick={() => openDetailsPanel(node)}>
        View Details
      </MenuItem>

      <MenuItem icon={<Copy />} onClick={() => copyToClipboard(node.concept_id)}>
        Copy ID
      </MenuItem>

      <Separator />

      <MenuItem icon={<EyeOff />} onClick={() => hideNode(node.concept_id)}>
        Hide from Graph
      </MenuItem>
    </ContextMenu>
  );
};

Benefits: - ✅ Fast navigation - no text input or embedding computation needed - ✅ Maintains user context - highlights show where you came from - ✅ Flexible - can replace or add to existing graph - ✅ Respects settings - uses current similarity threshold - ✅ Works across all explorers - universal interaction pattern

Restoration Priority: Phase 2 (Next)

Explorer-Specific Variations

While the core patterns above are universal, individual explorers may customize behaviors:

Force-Directed Graph: - Double-click node → Expand neighbors (additive) - Drag node → Reposition and pin to location - Empty area drag → Lasso selection (if enabled)

Hierarchical Tree: - Double-click node → Expand/collapse children - No node dragging (tree layout is computed) - Empty area interactions limited

Timeline Explorer: - Node click → Show concept details at that time point - Scrub timeline → View graph at specific date - Play button → Animate growth over time

Matrix/Heatmap: - Cell click → Show relationship details - Row/column click → Filter by concept - No spatial dragging (grid layout)

1. "You Are Here" Persistent Highlighter

Problem: When clicking a node to explore its neighborhood, the new graph loads and you lose track of which node you clicked on.

Solution: Maintain visual continuity across graph transitions with a persistent "origin node" indicator.

Implementation:

interface GraphState {
  focusedNodeId: string | null;  // The concept we're centered on
  originNodeId: string | null;    // The node that was clicked to get here
  previousFocusId: string | null; // For back button
}

// Visual indicators:
// - Origin node: Pulsing ring effect, distinct color (e.g., gold)
// - Focused node: Larger size, brighter color
// - Previous focus: Dashed outline (if still in graph)

Color Scheme: - Origin node (what you clicked): Gold/yellow pulsing ring - Current focus (center of graph): Bright blue, larger size - Regular nodes: Colored by ontology - Hovered node: White outline - Selected nodes: Solid ring

2. Navigation History (Breadcrumb Trail)

Concept: Track exploration path with visual breadcrumb trail

UI Location: Top of graph viewport, below search bar

Features: - Click any breadcrumb → Jump back to that concept - Shows concept labels (truncated if needed) - Maximum 5 visible crumbs (with "..." for older) - Clear history button

interface NavigationHistory {
  trail: Array<{
    conceptId: string;
    label: string;
    timestamp: Date;
  }>;
  currentIndex: number;
}

// Example breadcrumb UI:
// Home > AI Safety > Regulatory Framework > [Current Concept]

3. Back/Forward Navigation Buttons

Concept: Browser-style navigation for graph exploration

UI Location: Toolbar, next to search bar

Features: - Back button: Return to previous concept - Forward button: Re-visit next concept (after going back) - Keyboard shortcuts: Alt+Left (back), Alt+Right (forward) - Disabled when no history available

const useNavigationHistory = () => {
  const [history, setHistory] = useState<string[]>([]);
  const [currentIndex, setCurrentIndex] = useState(-1);

  const goBack = () => {
    if (currentIndex > 0) {
      setCurrentIndex(currentIndex - 1);
      return history[currentIndex - 1];
    }
  };

  const goForward = () => {
    if (currentIndex < history.length - 1) {
      setCurrentIndex(currentIndex + 1);
      return history[currentIndex + 1];
    }
  };

  const navigateTo = (conceptId: string) => {
    // Truncate forward history and add new entry
    const newHistory = history.slice(0, currentIndex + 1);
    newHistory.push(conceptId);
    setHistory(newHistory);
    setCurrentIndex(newHistory.length - 1);
  };

  return { goBack, goForward, navigateTo, canGoBack: currentIndex > 0, canGoForward: currentIndex < history.length - 1 };
};

4. Expand on Double-Click

Concept: Add neighbors to existing graph instead of replacing

Behavior: - Single click: Replace graph with new subgraph (current behavior) - Double click: Add clicked node's neighbors to existing graph - Shift+Double click: Add 2-hop neighbors

Use Case: Build up complex subgraphs incrementally without losing context

const handleNodeDoubleClick = async (nodeId: string) => {
  const neighbors = await fetchNeighbors(nodeId, { depth: 1 });

  // Merge new nodes/edges with existing graph
  setGraphData(prev => ({
    nodes: mergeNodes(prev.nodes, neighbors.nodes),
    links: mergeLinks(prev.links, neighbors.links),
  }));
};

5. Context Menu Reference

See "Universal Interaction Patterns" section above for complete context menu documentation.

The Universal Interaction Patterns section documents: - View Mode context menus (navigation and exploration) - Edit Mode context menus (see ADR-037: Human-Guided Graph Editing) - Follow Concept capability (lost feature to restore) - Explorer-specific variations

Key Actions: - View Mode: Follow, Add to Graph, Find Path To, View Details, Copy ID, Hide - Edit Mode: Connect Concepts, Flag as Invalid (see ADR-037)

Individual explorers may extend these base menus with explorer-specific actions (e.g., "Expand Children" in Hierarchical Tree, "Pin Node" in Force-Directed Graph).

6. Multi-Select and Bulk Actions

Concept: Select multiple nodes for batch operations

Interactions: - Ctrl+Click → Add node to selection - Shift+Click → Range select (all nodes between) - Click+Drag → Lasso select - Escape → Clear selection

Bulk Actions: - Export selected nodes - Find paths between selected nodes - Create custom subgraph from selection - Highlight selected neighborhood - Remove selected from view

7. Zoom-to-Fit and Focus Animations

Features: - Zoom to Fit button: Auto-zoom to show all nodes - Focus Animation: Smooth camera transition when clicking nodes - Highlight Flash: Brief pulse when focusing new node

const animateFocus = (nodeId: string) => {
  const node = findNode(nodeId);

  // Calculate zoom level to show node + 1-hop neighbors
  const neighborsBox = calculateBoundingBox([node, ...getNeighbors(nodeId)]);

  // Animate camera to focus on bounding box
  d3.transition()
    .duration(750)
    .ease(d3.easeCubicOut)
    .call(zoom.transform,
      d3.zoomIdentity
        .translate(width / 2, height / 2)
        .scale(calculateZoom(neighborsBox))
        .translate(-neighborsBox.centerX, -neighborsBox.centerY)
    );

  // Flash highlight on focused node
  d3.select(`#node-${nodeId}`)
    .transition()
    .duration(150)
    .attr('r', node.size * 2)
    .transition()
    .duration(150)
    .attr('r', node.size);
};

Implementation Patterns

Imperative DOM Manipulation for Dynamic Highlighting

Problem: React's declarative useEffect pattern can create timing issues when highlighting nodes in D3-rendered SVG graphs. Specifically: - Effects only run when dependencies change (clicking same node twice doesn't re-trigger) - Effects may run before D3 has positioned elements in the DOM - Graph data reloads clear the DOM, requiring re-application of highlights

Solution: Hybrid imperative/declarative approach combining React hooks with direct DOM manipulation.

Pattern:

// 1. Create imperative function with useCallback to apply styles directly to DOM
const applyHighlight = useCallback((nodeId: string, style: HighlightStyle) => {
  if (!svgRef.current || !settings.highlightEnabled) return;

  const svg = d3.select(svgRef.current);

  // Remove previous highlights
  svg.selectAll('circle.highlighted')
    .interrupt()
    .attr('stroke', '#fff')
    .attr('stroke-width', 2)
    .classed('highlighted', false);

  // Apply to target node using data-node-id attribute
  const targetCircle = svg.select<SVGCircleElement>(`circle[data-node-id="${nodeId}"]`);

  if (!targetCircle.empty()) {
    targetCircle
      .attr('stroke', style.color)
      .attr('stroke-width', style.width)
      .classed('highlighted', true);

    // Optional: Add animation
    if (style.animated) {
      const pulse = () => {
        targetCircle
          .transition()
          .duration(1000)
          .attr('stroke-width', style.width * 1.5)
          .attr('stroke-opacity', 0.6)
          .transition()
          .duration(1000)
          .attr('stroke-width', style.width)
          .attr('stroke-opacity', 1)
          .on('end', pulse);
      };
      pulse();
    }
  }
}, [settings.highlightEnabled]);

// 2. Call imperatively for immediate feedback (e.g., on click)
const handleNodeClick = (nodeId: string) => {
  setSelectedNodeId(nodeId);
  applyHighlight(nodeId, HIGHLIGHT_STYLES.origin); // Immediate visual update
};

// 3. Call declaratively from effect for persistence after data changes
useEffect(() => {
  if (!selectedNodeId) return;

  // Wait for DOM to be fully rendered after data reload
  const rafId = requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      applyHighlight(selectedNodeId, HIGHLIGHT_STYLES.origin);
    });
  });

  return () => {
    cancelAnimationFrame(rafId);
    // Cleanup highlights on unmount
    if (svgRef.current) {
      d3.select(svgRef.current)
        .selectAll('circle.highlighted')
        .interrupt()
        .attr('stroke', '#fff')
        .attr('stroke-width', 2)
        .classed('highlighted', false);
    }
  };
}, [selectedNodeId, data, applyHighlight]);

Key Techniques:

  1. Data Attributes for Selection: Add data-node-id attributes to DOM elements for reliable selection
  2. useCallback Memoization: Prevents function recreation, making it safe to include in effect dependencies
  3. Double requestAnimationFrame: Ensures DOM layout is complete before manipulation
  4. Interrupt Transitions: Stop ongoing animations before applying new ones
  5. CSS Classes for State: Use classes like .highlighted for easier debugging and cleanup

Benefits: - ✅ Works immediately on user interaction (no effect delay) - ✅ Persists across graph data changes - ✅ Handles same-node re-clicks correctly - ✅ No position tracking needed (styles the actual DOM element) - ✅ Graceful cleanup on unmount or settings changes

Application: Path Highlighting

Use Case: Visualize query results like "find path from Concept A to Concept B" by highlighting multiple nodes and edges along the route.

Example - Highlighting a Path:

interface PathHighlight {
  nodeIds: string[];
  edgeIds: string[];
  style: 'primary' | 'secondary' | 'alternate';
}

const applyPathHighlight = useCallback((path: PathHighlight) => {
  if (!svgRef.current) return;

  const svg = d3.select(svgRef.current);

  // Clear previous path highlights
  svg.selectAll('.path-highlight').classed('path-highlight', false);

  // Highlight nodes in path
  path.nodeIds.forEach((nodeId, index) => {
    const circle = svg.select<SVGCircleElement>(`circle[data-node-id="${nodeId}"]`);

    if (!circle.empty()) {
      circle
        .classed('path-highlight', true)
        .attr('stroke', PATH_COLORS[path.style])
        .attr('stroke-width', 4)
        .attr('stroke-dasharray', index === 0 || index === path.nodeIds.length - 1 ? 'none' : '5,5');
        // Dashed for intermediate nodes, solid for start/end
    }
  });

  // Highlight edges in path
  path.edgeIds.forEach(edgeId => {
    const line = svg.select<SVGLineElement>(`line[data-edge-id="${edgeId}"]`);

    if (!line.empty()) {
      line
        .classed('path-highlight', true)
        .attr('stroke', PATH_COLORS[path.style])
        .attr('stroke-width', 3)
        .attr('stroke-opacity', 0.8);
    }
  });

  // Optional: Animate path traversal
  animatePathTraversal(path.nodeIds, path.edgeIds);
}, []);

// Animate a "flow" effect along the path
const animatePathTraversal = (nodeIds: string[], edgeIds: string[]) => {
  let delay = 0;

  nodeIds.forEach((nodeId, index) => {
    setTimeout(() => {
      const circle = d3.select(`circle[data-node-id="${nodeId}"]`);
      circle
        .transition()
        .duration(300)
        .attr('r', (d: D3Node) => ((d.size || 10) * settings.visual.nodeSize) * 1.5)
        .transition()
        .duration(300)
        .attr('r', (d: D3Node) => (d.size || 10) * settings.visual.nodeSize);
    }, delay);

    delay += 400;
  });
};

Future Applications:

  1. Multi-Path Comparison: Show 3-5 paths simultaneously with different colors
  2. Primary path: Gold (#FFD700)
  3. Alternate path 1: Blue (#4A90E2)
  4. Alternate path 2: Green (#50C878)

  5. Confidence Visualization: Edge thickness proportional to relationship confidence

    .attr('stroke-width', (d) => 1 + (d.confidence * 4)) // 1-5px based on 0-1 confidence
    

  6. Interactive Path Exploration:

  7. Click node in path → Show evidence from source documents
  8. Hover edge → Show relationship type and confidence tooltip
  9. Right-click path → "Find alternate paths" or "Explain this connection"

  10. Breadcrumb Trail Visualization: Highlight your navigation history in the graph

    applyPathHighlight({
      nodeIds: navigationHistory.trail.map(h => h.conceptId),
      edgeIds: [], // Don't highlight edges for history
      style: 'secondary'
    });
    

  11. Query Result Highlighting: Show subgraph matching a pattern query

  12. Highlight nodes matching pattern
  13. Differentiate by role in pattern (subject, object, predicate)

Performance Considerations:

  • Batch DOM updates when highlighting many nodes/edges
  • Use CSS classes for bulk style changes when possible
  • Debounce frequent highlight changes (e.g., during animation scrubbing)
  • Consider virtual viewport for large graphs (only highlight visible elements)
// Batch update example
const applyBatchHighlight = (nodeIds: string[], style: HighlightStyle) => {
  const svg = d3.select(svgRef.current);

  // Single D3 selection update for all nodes
  svg.selectAll<SVGCircleElement, D3Node>('circle')
    .filter((d) => nodeIds.includes(d.id))
    .attr('stroke', style.color)
    .attr('stroke-width', style.width)
    .classed('highlighted', true);
};

Testing Checklist: - [ ] Highlights persist across graph reloads - [ ] Multiple paths can be highlighted simultaneously - [ ] Clicking same node re-applies highlight correctly - [ ] Animations can be interrupted/restarted cleanly - [ ] Cleanup occurs on unmount/setting toggle - [ ] Performance acceptable with 100+ node paths

Terminology

Explorer vs. Workbench: - Explorer: Interactive visualization mode (Force-Directed, Hierarchy, etc.) - Workbench: Query construction tool (Visual Query Builder, Path Finder, etc.)

We use "Explorer" to describe each interactive visualization mode. This term emphasizes the investigative, discovery-oriented nature of the tools and aligns with common graph database UI conventions (e.g., Neo4j Browser, graph explorers).

Implementation Priority

Phase 1 (Current): -  Force-Directed 2D Explorer -  Basic click-to-focus navigation -  Hover highlighting

Phase 2 (Next): - = "You Are Here" persistent highlighter - = Navigation history (back/forward buttons) - = Breadcrumb trail - = Right-click context menu (View Mode) - = Follow Concept capability (restore lost feature)

Phase 3: - Expand on double-click - Multi-select and bulk actions - Path Explorer workbench - Force-Directed 3D Explorer

Phase 4: - Hierarchical Tree Explorer - Timeline Explorer - Visual Query Builder - Ontology Comparator

Success Metrics

User Engagement: - Average exploration depth (clicks per session) - Time spent in visualization - Number of concepts explored per session

Discovery Metrics: - Unexpected connections found - Path queries executed - Concepts bookmarked/exported

Usability: - Navigation efficiency (time to find target) - Error rate (backtracking, confusion) - Feature adoption (% using history/context menu)

References

Approval & Sign-Off

  • [ ] Development Team Review
  • [ ] UX/UI Design Review
  • [ ] User Testing (navigation flows)
  • [ ] Documentation Complete