Frontend Spec: Interactive 2D Directed Acyclic Graph Canvas Composable (GraphCanvas)
Estado de fase (MASTER-00): MVP — alcance del MVP Shipaton sujeto a resultados de Gate 0 y PoC. Gobernanza: MASTER-00.
Frontend Spec: Interactive 2D Directed Acyclic Graph Canvas Composable (GraphCanvas)
Sección titulada «Frontend Spec: Interactive 2D Directed Acyclic Graph Canvas Composable (GraphCanvas)»1. Overview & Purpose
Sección titulada «1. Overview & Purpose»This specification details the core 2D graph visualizer (GraphCanvas) in :docugraph-app. It renders nodes and directed relationship edges retrieved via BE-03 buildGraph on a hardware-accelerated Compose Canvas. It supports smooth pan and pinch-zoom gestures (0.25x to 3.0x scale), spatial viewport pruning for 60 FPS performance, force-directed layout positioning, filter search queries, and node selection callbacks to launch FE-04 NodeInspector.
2. Component Signature & Props
Sección titulada «2. Component Signature & Props»package io.docugraph.app.ui.graph
import androidx.compose.runtime.Composableimport androidx.compose.ui.Modifier
@Composablefun GraphCanvas( projectId: String, viewModel: GraphViewModel, onNodeSelected: (nodeId: String) -> Unit, modifier: Modifier = Modifier)3. UI State Model & Data Classes
Sección titulada «3. UI State Model & Data Classes»package io.docugraph.app.ui.graph
import androidx.compose.ui.geometry.Offsetimport io.docugraph.backend.models.graph.GraphEdgeDtoimport io.docugraph.backend.models.graph.GraphNodeDto
data class ViewportTransform( val panOffset: Offset = Offset.Zero, val zoomScale: Float = 1.0f)
data class RenderNode( val data: GraphNodeDto, val position: Offset, val radius: Float = 24.0f, val isHighlighted: Boolean = false)
data class GraphCanvasUiState( val nodesMap: Map<String, RenderNode> = emptyMap(), val edges: List<GraphEdgeDto> = emptyList(), val transform: ViewportTransform = ViewportTransform(), val selectedNodeId: String? = null, val searchQuery: String = "", val isLoading: Boolean = false)4. Compose Custom Canvas Hierarchy & Gesture Handling
Sección titulada «4. Compose Custom Canvas Hierarchy & Gesture Handling»Box (Modifier.fillMaxSize().clipToBounds()) ├── Canvas (Modifier.fillMaxSize().pointerInput { detectTransformGestures & detectTapGestures }) │ ├── withTransform (translate(panX, panY), scale(zoomScale)) │ │ ├── Draw Edges: for edge in visibleEdges -> drawLine / drawPath (Directed Arrows) │ │ └── Draw Nodes: for node in visibleNodes -> drawCircle / drawText (Node Label & Color by document_type) └── Overlay Controls (Aligned to Top/Bottom): ├── TopSearchBar (OutlinedTextField search filter query) └── BottomToolbar (Zoom In Button, Zoom Out Button, Reset Viewport Button)5. Force-Directed Layout & Viewport Pruning (60 FPS Performance)
Sección titulada «5. Force-Directed Layout & Viewport Pruning (60 FPS Performance)»- Force-Directed Layout Algorithm:
- Repulsive Coulomb Force between nodes: $F_r = k^2 / d$
- Attractive Hooke Spring Force along directed edges: $F_a = d^2 / k$
- Iterate layout physics for 50 ticks upon loading
GraphDataResponse.
- Spatial Viewport Pruning:
- Calculate bounding box of current screen viewport transformed by
(panOffset, zoomScale). - Skip rendering edges and nodes whose spatial coordinates fall outside visible screen bounds.
- Calculate bounding box of current screen viewport transformed by
6. Document Type Color Scheme
Sección titulada «6. Document Type Color Scheme»| Document Type | Primary Node Color | Accent / Border Color |
|---|---|---|
user_story |
#4CAF50 (Green) |
#2E7D32 |
backend_task_spec |
#2196F3 (Blue) |
#1565C0 |
frontend_component_spec |
#9C27B0 (Purple) |
#6A1B9A |
database_schema |
#FF9800 (Orange) |
#E65100 |
mcp_specification |
#E91E63 (Pink) |
#AD1457 |
7. Verification & Test Plan
Sección titulada «7. Verification & Test Plan»- Performance Test: Render 500 nodes on desktop and mobile target, ensure frame render time $<16$ms (60 FPS).
- UiTest: Drag canvas (verify
panOffsetupdate), pinch-zoom (verify scale clamped between 0.25x and 3.0x), tap node (verifyonNodeSelectedinvoked with node ID).