aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/frontend/html/util
diff options
context:
space:
mode:
authorMtBntChvn <[email protected]>2026-02-21 08:11:41 +0000
committerMtBntChvn <[email protected]>2026-02-21 08:11:41 +0000
commite30933f55b7c9ea8eb6f30bd3d6c0d55a51f7971 (patch)
treef58bb6a140123317361608fbb9adef9afd6d853e /src/zenserver/frontend/html/util
parentfocus view on expanded node and fit children after expansion (diff)
downloadzen-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.js58
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)