diff options
| author | MtBntChvn <[email protected]> | 2026-02-21 08:11:41 +0000 |
|---|---|---|
| committer | MtBntChvn <[email protected]> | 2026-02-21 08:11:41 +0000 |
| commit | e30933f55b7c9ea8eb6f30bd3d6c0d55a51f7971 (patch) | |
| tree | f58bb6a140123317361608fbb9adef9afd6d853e /src/zenserver/frontend/html/util | |
| parent | focus view on expanded node and fit children after expansion (diff) | |
| download | zen-e30933f55b7c9ea8eb6f30bd3d6c0d55a51f7971.tar.xz zen-e30933f55b7c9ea8eb6f30bd3d6c0d55a51f7971.zip | |
replace fireworks expansion with ripple (concentric rings) placement
Children are now placed in concentric rings around the expanded node
like ripples in water, instead of a single arc at a large radius.
Ring placement:
- base_radius=70, ring_gap=40, min_node_gap=45
- Each ring fits floor(arc_length / min_gap) nodes
- Inner rings fill first, outer rings hold more nodes
- Semi-circle for non-root, full circle for root
Push-away reduced from 200+4n to a fixed 80px since the tight
rings no longer need a large clearance.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Diffstat (limited to 'src/zenserver/frontend/html/util')
| -rw-r--r-- | src/zenserver/frontend/html/util/graphengine.js | 58 |
1 files changed, 33 insertions, 25 deletions
diff --git a/src/zenserver/frontend/html/util/graphengine.js b/src/zenserver/frontend/html/util/graphengine.js index 009a568c2..4248e29f6 100644 --- a/src/zenserver/frontend/html/util/graphengine.js +++ b/src/zenserver/frontend/html/util/graphengine.js @@ -539,19 +539,16 @@ export class GraphEngine // snapshot existing nodes before adding children const existing = new Set(this._nodes); - // determine arc placement + // ripple placement: concentric rings around the expanded node const parent = this._find_parent(node); const outward_angle = parent ? Math.atan2(node.y - parent.y, node.x - parent.x) : 0; - const visible = Math.min(deps.length, MAX_VISIBLE_DEPS); - - // push non-root node away from parent to make room for children - // skip if already pushed once (collapse + re-expand) or re-expanding in place + // small push to clear immediate area (only on first expand) if (parent && !node._skip_push && !node._was_pushed) { - const push_dist = 200 + visible * 4; + const push_dist = 80; node.x += Math.cos(outward_angle) * push_dist; node.y += Math.sin(outward_angle) * push_dist; node.pinned = true; @@ -570,30 +567,41 @@ export class GraphEngine arc_span = Math.PI; } - const radius = 120 + visible * 2.5; - + // place children in concentric rings (ripples) + const base_radius = 70; + const ring_gap = 40; + const min_node_gap = 45; var added = 0; - for (const dep of deps) + var ring = 0; + + while (added < deps.length && added < MAX_VISIBLE_DEPS) { - if (added >= MAX_VISIBLE_DEPS) + const r = base_radius + ring * ring_gap; + const arc_len = arc_span * r; + const capacity = Math.max(1, Math.floor(arc_len / min_node_gap)); + const remaining = Math.min(deps.length, MAX_VISIBLE_DEPS) - added; + const batch = Math.min(capacity, remaining); + + for (var j = 0; j < batch; ++j) { - node.truncated = true; - break; + const dep = deps[added]; + const t = batch > 1 ? j / (batch - 1) : 0.5; + const angle = arc_start + t * arc_span; + const jitter = (Math.random() - 0.5) * 10; + const dep_node = this.add_node( + dep.opkey, + node.x + Math.cos(angle) * (r + jitter), + node.y + Math.sin(angle) * (r + jitter), + false + ); + dep_node.unresolved = dep.unresolved || false; + this.add_edge(node, dep_node, dep.dep_type); + added++; } - - const t = visible > 1 ? added / (visible - 1) : 0.5; - const angle = arc_start + t * arc_span; - const r = radius + Math.random() * 20; - const dep_node = this.add_node( - dep.opkey, - node.x + Math.cos(angle) * r, - node.y + Math.sin(angle) * r, - false - ); - dep_node.unresolved = dep.unresolved || false; - this.add_edge(node, dep_node, dep.dep_type); - added++; + ring++; } + if (added < deps.length) + node.truncated = true; // pin existing nodes during layout so only new children move for (const n of existing) |