@yuneta/gobj-ui 7.23.82 → 7.23.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,8 +51,10 @@
51
51
  *
52
52
  * `radial`: the root in the middle and a ring per depth, each
53
53
  * subtree owning a sector proportional to its leaves; the radius
54
- * of a ring grows until its neighbours sit side by side, so
55
- * nothing overlaps by construction (see `layout_radial`).
54
+ * of a ring grows until its neighbours sit side by side AND
55
+ * until it clears the ring inside it, so nothing overlaps by
56
+ * construction, across the ring or along the radius (see
57
+ * `layout_radial`).
56
58
  *
57
59
  * Pure: takes plain nodes and edges, returns positions. The G6
58
60
  * classes in c_g6_nodes_tree.js are two thin adapters over it.
@@ -272,6 +274,18 @@ function tidy_tree_lr(nodes, edges, o)
272
274
  return pos;
273
275
  }
274
276
 
277
+ /* How much of a card lies along the direction `a`: the projection
278
+ * of its rectangle on that direction. Full width when the card is
279
+ * read along its width, full height across it, and the honest
280
+ * mixture in between. */
281
+ function extent_at(n, a)
282
+ {
283
+ if(!n) {
284
+ return 0;
285
+ }
286
+ return Math.abs(n.w * Math.cos(a)) + Math.abs(n.h * Math.sin(a));
287
+ }
288
+
275
289
  /************************************************************
276
290
  * The radial tree: the root in the middle, a ring per depth.
277
291
  *
@@ -333,10 +347,10 @@ export function layout_radial(nodes, edges, opts)
333
347
  /* An explicit stack, not recursion: the sectors of a ring are
334
348
  * sorted by angle in pass 2, so the order they are pushed in does
335
349
  * not matter -- only that every node gets its own. */
336
- let sector = (root_id, root_from, root_span) => {
337
- let stack = [{id: root_id, from: root_from, span: root_span}];
350
+ let sector = (root_id, root_from, root_span, root_parent) => {
351
+ let stack = [{id: root_id, from: root_from, span: root_span, parent: root_parent}];
338
352
  while(stack.length) {
339
- let {id, from, span} = stack.pop();
353
+ let {id, from, span, parent} = stack.pop();
340
354
  let a = from + span / 2;
341
355
  angle.set(id, a);
342
356
  let ring = ring_of(id);
@@ -344,11 +358,21 @@ export function layout_radial(nodes, edges, opts)
344
358
  rings[ring] = [];
345
359
  }
346
360
  let n = t.by_id.get(id);
347
- rings[ring].push({id: id, angle: a, diag: Math.hypot(n.w, n.h)});
361
+ rings[ring].push({
362
+ id: id,
363
+ angle: a,
364
+ diag: Math.hypot(n.w, n.h),
365
+ /* How much of the card lies along the RADIUS at this
366
+ * angle -- the projection of the rectangle on that
367
+ * direction -- for itself and for the node it hangs
368
+ * from, which is the one it has to clear. */
369
+ ext: extent_at(n, a),
370
+ parent_ext: parent? extent_at(t.by_id.get(parent), a) : 0,
371
+ });
348
372
  let cursor = from;
349
373
  for(let kid of (t.children.get(id) || [])) {
350
374
  let part = span * leaves.get(kid) / leaves.get(id);
351
- stack.push({id: kid, from: cursor, span: part});
375
+ stack.push({id: kid, from: cursor, span: part, parent: id});
352
376
  cursor += part;
353
377
  }
354
378
  }
@@ -359,28 +383,43 @@ export function layout_radial(nodes, edges, opts)
359
383
  let cursor = -Math.PI / 2; /* the first child at the top */
360
384
  for(let kid of (t.children.get(root) || [])) {
361
385
  let part = 2 * Math.PI * leaves.get(kid) / leaves.get(root);
362
- sector(kid, cursor, part);
386
+ sector(kid, cursor, part, root);
363
387
  cursor += part;
364
388
  }
365
389
  } else {
366
390
  let cursor = -Math.PI / 2;
367
391
  for(let root of t.roots) {
368
392
  let part = 2 * Math.PI * leaves.get(root) / total;
369
- sector(root, cursor, part);
393
+ sector(root, cursor, part, null);
370
394
  cursor += part;
371
395
  }
372
396
  }
373
397
 
374
- /* Pass 2, the RADII: a ring is one step further out than the
375
- * ring before, and as far out as its two closest neighbours need
376
- * to sit side by side -- the arc between two adjacent centres is
377
- * r·Δangle, and it has to hold half of each card plus the gap.
378
- * Measured on NEIGHBOURS, not on the ring's total length: a
379
- * leafless root squeezed between two big subtrees gets a sliver
380
- * of angle, and it is the sliver that sets the radius. */
398
+ /* Pass 2, the RADII. Two constraints, and the larger wins.
399
+ *
400
+ * ACROSS the ring: its two closest neighbours have to sit side
401
+ * by side -- the arc between two adjacent centres is r·Δangle,
402
+ * and it has to hold half of each card plus the gap. Measured on
403
+ * NEIGHBOURS, not on the ring's total length: a leafless root
404
+ * squeezed between two big subtrees gets a sliver of angle, and
405
+ * it is the sliver that sets the radius.
406
+ *
407
+ * ALONG the radius: `ranksep` is a step between CENTRES, so a
408
+ * ring one step out was one step out whatever the cards
409
+ * measured -- 180 between the centres of two cards that reach
410
+ * 95 each way is two cards touching. The tidy tree never had
411
+ * this: its `ranksep` is a gap between COLUMNS, and a column
412
+ * carries its own width. Here every card is measured against
413
+ * the one it HANGS FROM, in the direction it hangs in -- the
414
+ * two are radially aligned, or nearly, since a child lives
415
+ * inside its parent's sector -- and the ring goes out far
416
+ * enough for the worst of them. */
381
417
  let radius = [0];
382
418
  for(let ring = 1; ring < rings.length; ring++) {
383
419
  let r = radius[ring - 1] + o.ranksep;
420
+ for(let n of (rings[ring] || [])) {
421
+ r = Math.max(r, radius[ring - 1] + (n.parent_ext + n.ext) / 2 + o.nodesep);
422
+ }
384
423
  let list = (rings[ring] || []).slice().sort((a, b) => a.angle - b.angle);
385
424
  for(let i = 0; i < list.length && list.length > 1; i++) {
386
425
  let a = list[i];
@@ -181,6 +181,21 @@ describe("the radial tree", () => {
181
181
  expect(d).toBeGreaterThan(190);
182
182
  });
183
183
 
184
+ test("a ring clears the ring inside it: a card does not sit on its parent", () => {
185
+ /* One child, straight below its root: `ranksep` alone is a
186
+ * step between CENTRES, so two cards 96 tall 100 apart had
187
+ * 48 + 48 of card and 4 of air. The ring adds the halves. */
188
+ let pos = layout_radial(
189
+ [N("root", 172, 96), N("kid", 172, 96)],
190
+ [{source: "root", target: "kid", rank: 0}],
191
+ {nodesep: 18, ranksep: 100}
192
+ );
193
+ let d = Math.hypot(
194
+ pos.get("kid").x - pos.get("root").x, pos.get("kid").y - pos.get("root").y
195
+ );
196
+ expect(d).toBeGreaterThanOrEqual(48 + 48 + 18);
197
+ });
198
+
184
199
  test("several roots share the circle around an empty centre", () => {
185
200
  let pos = layout_radial(
186
201
  [N("a", 100, 50), N("b", 100, 50), N("c", 100, 50)], [], {ranksep: 100}