feat(0037): placement direccional 45 grados de orphans (away from centroide)
Antes los hijos del mismo anchor se distribuian en un anillo de 360 grados alrededor del padre. Cuando un enricher producia 10+ hijos, se llenaban todas las direcciones y se pisaban nodos preexistentes. Ahora los hijos se reparten en un abanico de 45 grados (pi/4) saliendo del anchor en la direccion outward (vector anchor - centroide del resto del grafo). Si solo hay 1 nodo placed o coincide con el anchor, default a la derecha (0 rad). Capacidad por anillo restringida al arco (arc_span * r / min_dist), con fallback de subida de radio en mismo angulo si el slot ideal colisiona con un nodo no-orphan. Solo afecta la pasada 2 (orphans con anchor). Pasadas 1 y 3 intactas. build limpio, 102 pytest passed (WSL) + 91 passed/11 skipped (Windows). Refs: issues/0037-directional-orphan-placement.md
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
---
|
||||
id: 0037
|
||||
title: Placement direccional de orphans — abanico de 45 grados en una sola direccion
|
||||
status: done
|
||||
priority: high
|
||||
created: 2026-05-04
|
||||
completed: 2026-05-03
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
Hoy `place_orphans_near_neighbors` (en `main.cpp`) reparte los hijos del
|
||||
mismo anchor en un anillo de 360 grados alrededor del padre. Resultado:
|
||||
con muchos hijos, el viewport se llena en todas las direcciones,
|
||||
pisando nodos preexistentes y dificultando la lectura del grafo.
|
||||
|
||||
Issue 0035 ya cluster los hijos del mismo anchor (no los desperdiga
|
||||
por anillos crecientes), pero siguen ocupando 360 grados.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Que los nuevos hijos creados por un enricher (orphans con un anchor
|
||||
detectado por `layout_first_placed_neighbor`) se desplieguen en un
|
||||
**abanico de 45 grados** (no 360) en UNA sola direccion saliendo del
|
||||
anchor. Asi el grafo "crece" en una direccion clara por cada
|
||||
ejecucion de enricher, sin pisar lo existente.
|
||||
|
||||
## Diseño
|
||||
|
||||
### 1. Direccion del abanico — outward por defecto
|
||||
|
||||
Para cada anchor con orphans:
|
||||
- Calcular el centroide del resto de nodos placed del grafo (excluyendo
|
||||
los orphans propios y el anchor).
|
||||
- La direccion outward es el vector `anchor - centroide`, normalizado.
|
||||
- Si solo hay 1 nodo en el grafo (el propio anchor) o el centroide
|
||||
coincide con el anchor: usar direccion `(1, 0)` (derecha).
|
||||
|
||||
### 2. Reparto en abanico de 45 grados
|
||||
|
||||
`arc_span = π / 4` (45 grados, configurable como constante).
|
||||
|
||||
Para los N orphans del anchor:
|
||||
- Si `N == 1`: lo plantamos exactamente en la direccion outward al
|
||||
radio base (~80 px).
|
||||
- Si `N <= 8` (capacidad del primer anillo en 45 grados con min_dist):
|
||||
se reparten equiespaciados en el arco a un radio fijo.
|
||||
- Si `N > 8`: se llenan anillos sucesivos del mismo arco con radios
|
||||
crecientes (`80, 140, 200, 280, 400`) y misma capacidad por anillo.
|
||||
|
||||
Cada slot dentro del arco tiene angulo:
|
||||
```
|
||||
angle = out_angle - arc_span/2 + slot * (arc_span / (slot_count - 1))
|
||||
```
|
||||
(ajusta para cuando `slot_count == 1` para no dividir entre cero).
|
||||
|
||||
### 3. Colision con nodos preexistentes
|
||||
|
||||
Despues de calcular `(px, py)` para cada hijo, si colisiona con un
|
||||
nodo no-orphan, incrementar el indice de anillo (mismo angulo,
|
||||
radio mayor) hasta encontrar hueco o agotar anillos. En el ultimo
|
||||
recurso aceptar solape — coherente con la logica actual.
|
||||
|
||||
### 4. Fallback sin anchor
|
||||
|
||||
Los orphans sin anchor (no tienen vecino placed) siguen el camino
|
||||
existente: ring placement alrededor de la camara o parking lot.
|
||||
NO se les aplica el abanico.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Lanzar `split_words` (≥50 palabras unicas) sobre un nodo `text`:
|
||||
los 10 sueltos del preview + 1 Group caen en un abanico de 45
|
||||
grados saliendo del nodo `text`, en la direccion alejada del
|
||||
resto del grafo.
|
||||
- Lanzar el mismo enricher otra vez sobre OTRO nodo en otra
|
||||
posicion: el abanico nuevo apunta hacia su propia direccion
|
||||
outward.
|
||||
- Si el grafo solo tiene 1 nodo (sin existing centroide), el
|
||||
abanico sale a la derecha por default.
|
||||
- Tests pytest siguen verdes (no requiere tests nuevos — el cambio
|
||||
es algoritmico y se valida visualmente; opcional anyadir un test
|
||||
C++ standalone que verifique angulos contra una fixture).
|
||||
|
||||
## TBD
|
||||
|
||||
Branch `issue/0037-directional-orphan-placement`, merge `--no-ff` a
|
||||
master.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Animar la transicion del placement (poof-in suave). Fase 2.
|
||||
- Layout interno del Group cuando se expande — sigue siendo todos
|
||||
los hijos del Group ocultos en colapsado.
|
||||
- Grouping logic (eso es 0035, ya cerrado fase 1).
|
||||
Reference in New Issue
Block a user