docs(neo4j): ADR-001 graph viz lib + 200-page endpoint cap

Document the Cytoscape.js vs vis-network / Neovis / D3 / Reagraph analysis
for the graph view, and make the 200-page cap on /api/documents/{id}/graph
explicit (HTTP 413 + truncated flag beyond the cap).

Refs #186
This commit is contained in:
Pier-Jean Malandrino 2026-04-17 15:21:23 +02:00
parent 712fc3f1cd
commit 25a8794a0f
2 changed files with 146 additions and 4 deletions

View file

@ -0,0 +1,142 @@
# ADR-001: Graph visualization library for the Neo4j graph view
**Date**: 2026-04-17
**Status**: Proposed
**Deciders**: Pier-Jean Malandrino
## Context
v0.5.0 introduces Neo4j as a graph-native storage layer for parsed documents
(see [docs/design/neo4j-integration.md](../../design/neo4j-integration.md)
and [#186](https://github.com/scub-france/Docling-Studio/issues/186)). We need
an in-app visualization of that graph: the `DoclingDocument` tree as rendered
in Neo4j, with nodes colored by element type (`SectionHeader`, `Paragraph`,
`Table`, `Figure`, `ListItem`, `Formula`) and edges (`PARENT_OF`, `NEXT`,
`ON_PAGE`, `HAS_CHUNK`, `DERIVED_FROM`).
The view lives in the existing Vue 3 debug panel. It is the **primary demo
artifact** for the Hackernoon hackathon (Neo4j partner), so polish matters as
much as correctness.
### Constraints
- Vue 3 + Vite frontend, no framework change
- Must render the full tree of a 200-page document (worst case ≈ a few
thousand nodes; see graph endpoint cap in the design doc §8.4)
- Needs a **clean hierarchical layout** — documents are trees, not arbitrary
graphs; a good tree layout is the single biggest UX lever
- Needs per-node styling (shape + color by label), click, hover, zoom, pan
- Must be installable without Java/Python-side changes
- License compatible with the repo (MIT-ish preferred)
### Non-goals for v0.5.0
- 3D rendering
- Force-directed simulation as the primary layout (we have a tree)
- Editing nodes in place (read-only view)
- Rendering millions of nodes
## Decision
Use **Cytoscape.js** via a thin Vue wrapper (`vue-cytoscape` or a bespoke
`GraphView.vue` that imports `cytoscape` directly and uses the
`dagre`/`breadthfirst` layouts).
## Consequences
### Positive
- Battle-tested library (13k+ GitHub stars, maintained since 2013, used by
Neo4j's own "Bloom"-style visualizations in the community)
- First-class support for hierarchical layouts via `cytoscape-dagre` (hub-and-
spoke / tree) and built-in `breadthfirst` — both map naturally to our
`PARENT_OF` structure
- CSS-like selector syntax for styling (`node[label = "Table"] { ... }`),
which is pleasant to evolve as we add node types
- Permissive licensing (MIT)
- Headless mode available, so it can be tested outside a DOM (Jest + jsdom
works cleanly)
- Active ecosystem: `cytoscape-cola`, `cytoscape-klay`, `cytoscape-popper` for
tooltips, all maintained
- Bundle size is reasonable for a demo: ~300 KB min+gz for core + dagre, well
below our current frontend budget
### Negative
- Styling DSL is powerful but has its own syntax to learn; not plain CSS
- Large graphs (>10k nodes) benefit from canvas+WebGL libraries
(sigma.js, reagraph) — we are explicitly not in that regime for v0.5, but
we would need to swap if we later visualize the cross-document graph
- No Vue 3 component library that is both maintained and popular — we wrap it
ourselves in `GraphView.vue` (the wrapper is ~50 LOC, so this is minor)
### Neutral
- Not "Neo4j-branded": we do not use Neovis.js, which is a thin Cytoscape
wrapper around the Bolt protocol. Our graph API already returns shaped
JSON, so the Neovis convenience is not worth the lock-in
- We take on one runtime dependency (`cytoscape` + `cytoscape-dagre`)
## Alternatives Considered
### Alternative 1: vis-network (vis.js)
- **Pros**: Very easy to get started, built-in physics, shipped by Neo4j
Browser historically
- **Cons**: Maintenance has been rocky (original vis.js split into several
forks; `vis-network` is the maintained branch but releases are sparse);
hierarchical layout is OK but less configurable than dagre; styling API is
less expressive; TypeScript types lag behind the JS API
- **Why rejected**: Hierarchical layout quality is the single most important
criterion for a document tree, and vis-network is clearly a notch below
Cytoscape + dagre here. Maintenance trajectory is also a concern for a
release we want to keep shipping on
### Alternative 2: Neovis.js
- **Pros**: Built by Neo4j Labs, connects directly to a Bolt endpoint, nice
out-of-the-box "Neo4j look"
- **Cons**: Wraps Cytoscape anyway, so everything it can do we can do with
Cytoscape directly; expects the browser to talk Bolt, which forces us to
expose Neo4j creds in the frontend OR to proxy Bolt through the backend
(both worse than our current "backend returns JSON" design); limited
customization compared to raw Cytoscape
- **Why rejected**: The auth story is a non-starter for a hackathon demo we
want to show publicly, and we lose nothing vs. Cytoscape by going one
layer lower
### Alternative 3: D3 (d3-hierarchy + d3-force)
- **Pros**: Maximum flexibility; beautiful, publication-grade output; full
SVG control
- **Cons**: Much more code for the same result — layout, zoom, pan, hover,
selection all hand-rolled; steeper learning curve for future contributors
to the project; no built-in graph data model
- **Why rejected**: We're building a product feature, not a data-viz
artefact. The time budget (1 day of Day 3) doesn't fit a D3 build-your-own
### Alternative 4: Reagraph / react-force-graph / sigma.js (WebGL)
- **Pros**: Scales to tens of thousands of nodes at 60 FPS; good for future
cross-document visualization
- **Cons**: Optimized for force-directed layouts, weaker hierarchical
support; Reagraph is React-only (requires a React island inside Vue);
sigma.js's tree layout is immature
- **Why rejected**: Wrong regime for a single-document tree. Worth
reconsidering if/when we visualize the full corpus graph in a later release
### Alternative 5: Mermaid
- **Pros**: Trivial to embed, already used in docs
- **Cons**: Static rendering, no interactivity, not designed for thousands of
nodes, no per-node click/hover
- **Why rejected**: A viewer, not a visualizer. We need interactivity
## References
- [Neo4j integration design doc](../../design/neo4j-integration.md) §8.3
- [Issue #186 — Neo4j integration](https://github.com/scub-france/Docling-Studio/issues/186)
- [Cytoscape.js](https://js.cytoscape.org/)
- [cytoscape-dagre](https://github.com/cytoscape/cytoscape.js-dagre)
- [vis-network](https://visjs.github.io/vis-network/docs/network/)
- [Neovis.js](https://github.com/neo4j-contrib/neovis.js)

View file

@ -232,8 +232,8 @@ DETACH DELETE d
- Create `(:Chunk)-[:DERIVED_FROM]->(:Element)` via source element `self_ref`
- Do NOT duplicate embeddings (stay in OpenSearch, keep `embedding_ref`)
- [ ] Frontend: new "Graph view" tab in debug panel
- Vue component with `cytoscape` (lighter, better layout API)
- FastAPI endpoint `/api/documents/{doc_id}/graph` returns nodes + edges around a scope (whole doc or subtree)
- Vue component with `cytoscape` (lighter, better layout API — see [ADR-001](../architecture/adrs/ADR-001-graph-visualization-library.md))
- FastAPI endpoint `/api/documents/{doc_id}/graph` returns full nodes + edges for the document, **capped at 200 pages** (HTTP 413 beyond; pagination deferred to v0.6). The endpoint must include a `truncated: bool` flag and `node_count` / `edge_count` in the response envelope so the UI can warn the user cleanly.
- View: vertical tree, colors per node type, click-to-zoom, hover details
- [ ] Per-document "Graph-ready" / "RAG-ready" badge in list
- [ ] README update:
@ -337,10 +337,10 @@ volumes:
- Rec: OpenSearch ref (avoid duplication; OpenSearch remains source of truth for vectors). In v0.6+, consider native Neo4j vector index.
3. **Graph view UI: cytoscape or vis-network ?**
- Rec: **cytoscape** — lighter, better layout API, used by Neo4j itself.
- Decided: **Cytoscape.js** — see [ADR-001](../architecture/adrs/ADR-001-graph-visualization-library.md) for the full analysis.
4. **Graph endpoint: return full doc or paginate ?**
- Rec: full doc for v0.5 (reasonable cap at 200 pages). Pagination in v0.6.
- Decided: full doc for v0.5, **hard cap at 200 pages**. Beyond the cap, the endpoint returns HTTP 413 with a `truncated: true` flag; the UI shows "Graph too large to render — reduce scope". Pagination ships in v0.6.
5. **Error strategy**: if Neo4j is down at ingestion, fail or degrade gracefully ?
- Rec: **fail fast** for v0.5 (avoid silent inconsistencies). `neo4j_required: bool` config option.