# ADR-110: CrawlQ-Quality Graph Visualization as Reusable Component

**Date:** 2026-03-16 | **Status:** ACCEPTED

## Context

The Graqle Studio Graph Explorer had a basic D3 force graph with:
- No labels on nodes (hidden for graphs > 200 nodes)
- Straight lines for edges (no visual hierarchy)
- No zoom controls
- No hover highlighting of connected subgraph
- 3-second timeout that failed on the 2.8MB graph response
- Called Lambda through Amplify SSR proxy (17 seconds round-trip)

CrawlQ Studio's `BrandKGGraph` (at `C:\Users\haris\Studio_CrawlQ\brandio-frontend\src\components\bamr\intelligence\brand-kg-graph.tsx`) has a production-quality graph that users find visually compelling — the "I need to show this to my team" reaction.

The user requested: "It must be the same graph type as we have used in CrawlQ Studio" and "we can use this type of graph in other places too."

## Decision

Port the CrawlQ `BrandKGGraph` D3 implementation to Graqle Studio as a reusable `ForceGraph` component. The same component will be used for:
1. **Graph Explorer** (`/dashboard/graph`) — full interactive graph
2. **Intelligence Dashboard** (`/dashboard/intelligence`) — module dependency subgraph
3. **Impact Blast Radius** — concentric ring visualization
4. **Landing Page** — demo graph for unauthenticated visitors
5. **Control Plane** — mini graphs per project instance

### Key Visual Features (Ported from CrawlQ)

| Feature | CrawlQ Implementation | Graqle Adaptation |
|---------|----------------------|-------------------|
| Node fill | Radial gradient per entity color | Same — `getNodeColor()` maps types |
| Node sizing | Connection count + confidence bonus | Connection count + `size` from backend |
| Glass reflection | Inner circle at (-25%, -25%) | Same |
| Link style | Curved quadratic bezier paths | Same |
| Arrows | Confidence-tiered markers | Single marker (no confidence on edges yet) |
| Link labels | Visible on hover, hidden by default | Same |
| Hover effect | Expand node + highlight connected edges + dim unconnected | Same |
| Labels | Background rect + text, zoom-adaptive | Same |
| Auto-fit | `fitToView()` after simulation end + 1.5s timer | Same (2s timer for larger graph) |
| Zoom controls | +/−/FIT buttons, zoom level indicator | Same |

### Performance Optimization

- **Direct Function URL loading**: Browser → Lambda Function URL (2-3s for 2.8MB) instead of Browser → Amplify SSR → Lambda (17s)
- **Zoom-adaptive labels**: Labels hidden at low zoom on large graphs, shown when zoomed in
- **Alpha decay tuning**: `alphaDecay(0.04)` for graphs > 1000 nodes (faster stabilization)
- **Collision radius scaling**: Tighter spacing for large graphs

### Data Flow (Function URL Direct)

```
Browser (graqle.com)
    ↓ fetch() — direct to Lambda Function URL (CORS: AllowOrigins: ["*"])
Lambda Function URL (eu-central-1)
    ↓ reads graph from memory (loaded from S3 JSON / Neptune)
Returns 2.8MB JSON ({nodes: [...], links: [...]})
    ↓
D3 ForceGraph renders in browser
```

No Amplify SSR proxy in the data path for the graph visualization. The Function URL has CORS configured for all origins.

## Consequences

### Positive
- Visual parity with CrawlQ — same "wow factor"
- Reusable component for all graph visualizations in Graqle
- 8x faster loading (2s vs 17s)
- Same D3.js dependency (no new libraries needed)

### Negative
- Lambda Function URL is exposed publicly (but graph data is not sensitive — it's code architecture)
- Large SVG DOM for 4,892 nodes — may need WebGL/Canvas fallback for 10K+ graphs
- Direct Function URL coupling in frontend (should be configurable via env var for multi-environment)

### Future
- When Neptune is live (ADR-108), the same Function URL will serve graph data from Neptune instead of JSON
- WebGL renderer (`react-force-graph-2d` already in package.json) for graphs > 10K nodes
